简体   繁体   中英

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!

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM