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
and r2
are CompletableFuture
s for two independently submitted async tasks.
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
It doesn't. By the time you call the println
on these instances, r2
and r3
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 a CompletableFuture
of its own that will be completed when all the CompletableFuture
you gave them are done. You chain that into another CompletableFuture
with thenRun
which, since r2
and r3
are done, simply depends on r1
, ie. it is completed when r1
completes.
You choose to discard the reference to this CompletableFuture
but the task submitted to thenRun
is scheduled. If you add a
Thread.sleep(6000);
at the end of your original program, you'll see your End.
log printed when r1
completes and, consequently, the one returned by thenRun
.
Note that, unless otherwise specified, your async tasks within the CompletableFuture
(eg. submitted through supplyAsync
) are all ran within the default ForkJoinPool
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With