When is CompletableFuture actually completed?

13.4k views Asked by At

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]
1

There are 1 answers

2
Sotirios Delimanolis On BEST ANSWER

r1 and r2 are CompletableFutures 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.