I had a question while studying CompletableFuture
. The get()
/join()
methods are blocking calls. What if I don't call either of them?
This code calls get()
:
// Case 1 - Use get()
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello");
}).get();
System.out.println("World!");
Thread.sleep(5_000L); // Don't finish the main thread
Output:
Hello
World!
This code calls neither get()
nor join()
:
// Case 2 - Don't use get()
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello");
});
System.out.println("World!");
Thread.sleep(5_000L); // For don't finish main thread
Output:
World!
Hello
I don't know why the runnable block of case 2 is working.
The entire idea of CompletableFuture
is that they are immediately scheduled to be started (though you can't reliably tell in which thread they will execute), and by the time you reach get
or join
, the result might already be ready, i.e.: the CompletableFuture
might already be completed. Internally, as soon as a certain stage in the pipeline is ready, that particular CompletableFuture
will be set to completed. For example:
String result =
CompletableFuture.supplyAsync(() -> "ab")
.thenApply(String::toUpperCase)
.thenApply(x -> x.substring(1))
.join();
is the same thing as:
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "ab");
CompletableFuture<String> cf2 = cf1.thenApply(String::toUpperCase);
CompletableFuture<String> cf3 = cf2.thenApply(x -> x.substring(1));
String result = cf3.join();
By the time you reach to actually invoke join
, cf3
might already finish. get
and join
just block until all the stages are done, it does not trigger the computation; the computation is scheduled immediately.
A minor addition is that you can complete a CompletableFuture
without waiting for the execution of the pipelines to finish: like complete
, completeExceptionally
, obtrudeValue
(this one sets it even if it was already completed), obtrudeException
or cancel
. Here is an interesting example:
public static void main(String[] args) {
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println("started work");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
System.out.println("done work");
return "a";
});
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
cf.complete("b");
System.out.println(cf.join());
}
This will output:
started work
b
So even if the work started, the final value is b
, not a
.
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