Here's the MCVE:
public static void main(String[] args) {
CompletableFuture<String> r1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "41";
});
CompletableFuture<String> r2 = CompletableFuture.supplyAsync(() -> "42");
CompletableFuture<String> r3 = CompletableFuture.supplyAsync(() -> {
System.out.println("I'm called.");
return "43";
});
CompletableFuture.allOf(r1, r2, r3).thenRun(() -> { System.out.println("End."); });
Stream.of(r1, r2, r3).forEach(System.out::println);
}
Somewhat curiously, without actually completing the CompletableFuture
from allOf(...)
, e.g. calling its join()
, I get the following output:
I'm called.
java.util.concurrent.CompletableFuture@<...>[Not completed, 1 dependents]
java.util.concurrent.CompletableFuture@<...>[Completed normally]
java.util.concurrent.CompletableFuture@<...>[Completed normally]
May I know what's causing the JVM to treat/think that r1
has 1 (estimated number of) dependent CompletableFuture
, while it decides to straightforwardly complete r2
and r3
? The only difference I can see is just the try-catch
, so is the answer as simple as that?
For comparison, I get the expected waiting time of 5 seconds and the following output when I actually do a join()
at the end. If it helps, I'm encountering this on Java 8 Update 40 JVM.
Modification:
// ...
CompletableFuture.allOf(r1, r2, r3).thenRun(() -> { System.out.println("End."); }).join();
Stream.of(r1, r2, r3).forEach(System.out::println);
Output:
I'm called.
// <note: 5-second wait is here>
End.
java.util.concurrent.CompletableFuture@<...>[Completed normally]
java.util.concurrent.CompletableFuture@<...>[Completed normally]
java.util.concurrent.CompletableFuture@<...>[Completed normally]
r1
andr2
areCompletableFuture
s for two independently submitted async tasks.It doesn't. By the time you call the
println
on these instances,r2
andr3
have completed normally (they don't do much).r1
hasn't (the thread that would have completed it is most likely sleeping).The call to
allOf
is not blocking. It will return aCompletableFuture
of its own that will be completed when all theCompletableFuture
you gave them are done. You chain that into anotherCompletableFuture
withthenRun
which, sincer2
andr3
are done, simply depends onr1
, ie. it is completed whenr1
completes.You choose to discard the reference to this
CompletableFuture
but the task submitted tothenRun
is scheduled. If you add aat the end of your original program, you'll see your
End.
log printed whenr1
completes and, consequently, the one returned bythenRun
.Note that, unless otherwise specified, your async tasks within the
CompletableFuture
(eg. submitted throughsupplyAsync
) are all ran within the defaultForkJoinPool
which uses daemon threads. Your application will exit before the the 5s has elapsed unless you choose to block somewhere and wait for that time to pass.