Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

will CompletableFuture callback always be executed

I have two service calls:

String call1() { ... return "ok"; }
void call2(String) { ... }

I know the basic way for CompletableFuture with callback is like

CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> call1())
.thenAccept(s -> call2(s));
future.join();

What if I separate the two chained CompletableFutures, like:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join(); // will call2 be executed at this time?

Is this any different from calling join() on future2:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future2.join();

What if I call join() on both of the futures?

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join();
future2.join();

It seems they are all the same from running my sample code. But I feel something might be wrong somewhere. Thanks!

like image 691
lznt Avatar asked Oct 14 '25 16:10

lznt


2 Answers

They are not the same.

In short, you can look at it as future1 and future2 hold results of distinct tasks (even if future2 uses the result of future1, it's a different future).

future1.join() will block until () -> call1() ends, and future2's task won't start until then. future2.join() will wait until s -> call2(s) is done.

What if I separate the two chained CompletableFutures, like:

This makes no difference as far as task execution is concerned. It's either a question of style or it only matters when you need to use the two future objects separately.

What if I call join() on both of the futures?

It's redundant in this case to call future1.join() as you are not doing anything between the two .join calls. It would make sense if you wanted to perform some action "after completion of task1 and before the completion of task 2".
In this case, though, calling future2.join() is enough.


And the code snippet below should show how this behaves:

public static void main(String[] args) {
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> delay());
    CompletableFuture<Void> future2 = future1.thenRun(() -> delay());

    long start = System.currentTimeMillis();

    future1.join();
    System.out.println("Future 1 done. Waiting for future 2: " 
            + (System.currentTimeMillis() - start));

    future2.join();
    System.out.println("Future 2 complete: " 
            + (System.currentTimeMillis() - start));
}

static void delay() {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

As it is, this code outputs:

Future 1 done. Waiting for future 2: 5001
Future 2 complete: 10001

But when you remove future1.join(), the output becomes:

Future 2 complete: 10001

Which simply means that future1.join() is superfluous unless you have actions to perform between the completions of the two futures

like image 131
ernest_k Avatar answered Oct 17 '25 05:10

ernest_k


The supplyAsync and thenAccept methods should be executed on a separate thread automatically according to the documentation:

All async methods without an explicit Executor argument are performed using the ForkJoinPool.commonPool()

In your examples, the only difference is your thread waits on different events before continuing due to the joining. Here is the breakdown:

CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> call1())
.thenAccept(s -> call2(s));
future.join();

This will wait until call2 completes because that is the future returned by thenAccept method.

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join(); // will call2 be executed at this time?

This will only wait until call1 completes and moves on. call2 will still gets executed.

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future2.join();

This is identical to the first one.

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join();
future2.join();

Calling future1.join() ends when call1 completed, then future2.join() ends when call2 completed. This should be identical functionally with the first one as well.

like image 35
Magdrop Avatar answered Oct 17 '25 06:10

Magdrop



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!