Explanation
What just happened
submit() returned a Future straight away, and main kept working. When main called f.get(), the result wasn’t ready, so main parked (WAITING) until the pool thread finished slowCall().
Try the what-if switch
get() too early: calling get() straight after submit() makes main wait at once, and doOtherWork() can only start after slowCall(). The code still uses a pool, but nothing overlaps, so it’s as slow as doing both in a row.
A Future is a receipt
Think of it like a ticket at a coffee counter. You get it at once, and you can do other things. get() is walking to the counter and standing there until your order is ready.
Use it well
- Start the slow work as early as possible and call
get()as late as possible. The gap between them is the time you save. - Use a timeout, so a stuck task can’t freeze the caller forever:
String result = f.get(2, TimeUnit.SECONDS); // throws TimeoutException
Its limits
With a plain Future, the only way to use the result is to block on it. You can’t say “when this is done, do that”. That’s what CompletableFuture adds, in the next scenarios.
Common questions
Does Future.get() block?
Yes. If the result isn't ready, get() parks the calling thread (WAITING) until the task finishes.
Why isn't my ExecutorService code any faster?
If you call get() right after submit(), the caller waits at once and nothing overlaps. Start slow work as early as you can and call get() as late as you can.
How do I add a timeout to Future.get()?
Use get(2, TimeUnit.SECONDS). It throws TimeoutException when time runs out, so a stuck task can't freeze the caller forever.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us