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]
r1andr2areCompletableFutures for two independently submitted async tasks.It doesn't. By the time you call the
printlnon these instances,r2andr3have completed normally (they don't do much).r1hasn't (the thread that would have completed it is most likely sleeping).The call to
allOfis not blocking. It will return aCompletableFutureof its own that will be completed when all theCompletableFutureyou gave them are done. You chain that into anotherCompletableFuturewiththenRunwhich, sincer2andr3are done, simply depends onr1, ie. it is completed whenr1completes.You choose to discard the reference to this
CompletableFuturebut the task submitted tothenRunis scheduled. If you add aat the end of your original program, you'll see your
End.log printed whenr1completes 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 defaultForkJoinPoolwhich 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.