89

Suppose I have the following code:

CompletableFuture<Integer> future  
        = CompletableFuture.supplyAsync( () -> 0);

thenApply case:

future.thenApply( x -> x + 1 )
      .thenApply( x -> x + 1 )
      .thenAccept( x -> System.out.println(x));

Here the output will be 2. Now in case of thenApplyAsync:

future.thenApplyAsync( x -> x + 1 )   // first step
      .thenApplyAsync( x -> x + 1 )   // second step
      .thenAccept( x -> System.out.println(x)); // third step

I read in this blog that each thenApplyAsync are executed in a separate thread and 'at the same time'(that means following thenApplyAsyncs started before preceding thenApplyAsyncs finish), if so, what is the input argument value of the second step if the first step not finished?

Where will the result of the first step go if not taken by the second step? the third step will take which step's result?

If the second step has to wait for the result of the first step then what is the point of Async?

Here x -> x + 1 is just to show the point, what I want know is in cases of very long computation.

Lii
  • 11,553
  • 8
  • 64
  • 88
Yulin
  • 1,163
  • 1
  • 7
  • 10
  • Did you try this in your IDE debugger? Seems you could figure out what is happening quite easily with a few well-placed breakpoints. – Jim Garrison Nov 25 '17 at 19:02
  • 2
    Interesting question! I added some formatting to your text, I hope that is okay. Note that you can use "`" around inline code to have it formatted as code, and you need an empty line to make a new paragraph. – Lii Nov 25 '17 at 19:33
  • 1
    Not except the 'thenApply' case, I'm new to concurrency and haven't had much practice on it, my naïve impression is that concurrent code problems are hard to track, so instead of try it myself I hope some one could give me a definitive answer on it to clear my confusions. @JimGarrison – Yulin Nov 26 '17 at 16:22
  • Thanks for your corrections. @Lii – Yulin Nov 26 '17 at 16:28
  • @Yulin Hello again! I'd like to remind you that you should accept one of the answers if you think it solved your problem. That helps people see that the question does not need more attention. You also get a few points yourself by accepting an answer, and people may be more willing to answer your questions in the future. Here is some information: https://stackoverflow.com/help/accepted-answer – Lii Dec 10 '17 at 10:37
  • 1
    @Lii Didn't know there is a accept answer operation, now one answer is accepted. Thanks! – Yulin Dec 10 '17 at 15:32

6 Answers6

82

The difference has to do with the Executor that is responsible for running the code. Each operator on CompletableFuture generally has 3 versions.

  1. thenApply(fn) - runs fn on a thread defined by the CompleteableFuture on which it is called, so you generally cannot know where this will be executed. It might immediately execute if the result is already available.
  2. thenApplyAsync(fn) - runs fn on a environment-defined executor regardless of circumstances. For CompletableFuture this will generally be ForkJoinPool.commonPool().
  3. thenApplyAsync(fn,exec) - runs fn on exec.

In the end the result is the same, but the scheduling behavior depends on the choice of method.

Kiskae
  • 24,655
  • 2
  • 77
  • 74
  • 18
    Nice answer, it's good to get an explanation about all the difference version of `thenApply`. But I think you miss this part of the question: "What's the point?" When and why would one use `thenApplyAsync` instead of `thenApply`? They will run at the same time, won't they? The difference seem to be only on which thread they run, but that should not matter much, since both probably run on some thread pool thread anyway. – Lii Nov 25 '17 at 19:37
  • 3
    It is a chain, every call in the chain depends on the previous part having completed. You use `thenApplyAsync` if you need to execute the function on a pre-defined executor. An example would be execution on the UI thread when dealing with UI updates as a response to the result of the future. – Kiskae Nov 25 '17 at 20:12
  • Is it that compared to 'thenApply', 'thenApplyAsync' dose not block the current thread and no difference on other aspects? Assume the task is very expensive. – Yulin Nov 26 '17 at 16:44
  • 7
    `thenApplyAsync` is guarenteed to not block the current thread, whilst `thenApply` depends on the object it is called on for its behavior. – Kiskae Nov 26 '17 at 19:12
  • Kiskae I just ran this experiment calling thenApply on a CompletableFuture and thenApply was executed on a different thread. Are you sure your explanation is correct? Maybe I didn't understand correctly. Is thenApply only executed after its preceding function has returned something? If so, doesn't it make sense for thenApply to always be executed on the same thread as the preceding function? This way, once the preceding function has been executed, its thread is now free to execute thenApply. Am I missing something here? – Boris Aug 22 '18 at 16:46
  • @Boris As the answer said, it is completely implementation dependant. When I made this answer it executes on the calling thread if the object is already complete, otherwise it queues the callback and runs it on the thread that completes the object. – Kiskae Aug 23 '18 at 12:04
  • What is the "thread that completes the object"? Can you give an example with code? I re ran my experiment and I realized I may have misinterpreted my logs; it seems thenApply gets executed on the same thread as SupplyAsync regardless of whether it is called before the SupplyAsync function has been executed. – Boris Aug 24 '18 at 14:36
47

You're mis-quoting the article's examples, and so you're applying the article's conclusion incorrectly. I see two question in your question:

What is the correct usage of .then___()

In both examples you quoted, which is not in the article, the second function has to wait for the first function to complete. Whenever you call a.then___(b -> ...), input b is the result of a and has to wait for a to complete, regardless of whether you use the methods named Async or not. The article's conclusion does not apply because you mis-quoted it.

The example in the article is actually

CompletableFuture<String> receiver = CompletableFuture.supplyAsync(this::findReceiver);

receiver.thenApplyAsync(this::sendMsg);  
receiver.thenApplyAsync(this::sendMsg);  

Notice the thenApplyAsync both applied on receiver, not chained in the same statement. This means both function can start once receiver completes, in an unspecified order. (Any assumption of order is implementation dependent.)

To put it more clearly:

a.thenApply(b).thenApply(c); means the order is a finishes then b starts, b finishes, then c starts.
a.thenApplyAsync(b).thenApplyAsync(c); will behave exactly the same as above as far as the ordering between a b c is concerned.

a.thenApply(b); a.thenApply(c); means a finishes, then b or c can start, in any order. b and c don't have to wait for each other.
a.thenApplyAync(b); a.thenApplyAsync(c); works the same way, as far as the order is concerned.

You should understand the above before reading the below. The above concerns asynchronous programming, without it you won't be able to use the APIs correctly. The below concerns thread management, with which you can optimize your program and avoid performance pitfalls. But you can't optimize your program without writing it correctly.


As titled: Difference between thenApply and thenApplyAsync of Java CompletableFuture?

I must point out that the people who wrote the JSR must have confused the technical term "Asynchronous Programming", and picked the names that are now confusing newcomers and veterans alike. To start, there is nothing in thenApplyAsync that is more asynchronous than thenApply from the contract of these methods.

The difference between the two has to do with on which thread the function is run. The function supplied to thenApply may run on any of the threads that

  1. calls complete
  2. calls thenApply on the same instance

while the 2 overloads of thenApplyAsync either

  1. uses a default Executor (a.k.a. thread pool), or
  2. uses a supplied Executor

The take away is that for thenApply, the runtime promises to eventually run your function using some executor which you do not control. If you want control of threads, use the Async variants.

If your function is lightweight, it doesn't matter which thread runs your function.

If your function is heavy CPU bound, you do not want to leave it to the runtime. If the runtime picks the network thread to run your function, the network thread can't spend time to handle network requests, causing network requests to wait longer in the queue and your server to become unresponsive. In that case you want to use thenApplyAsync with your own thread pool.


Fun fact: Asynchrony != threads

thenApply/thenApplyAsync, and their counterparts thenCompose/thenComposeAsync, handle/handleAsync, thenAccept/thenAcceptAsync, are all asynchronous! The asynchronous nature of these function has to do with the fact that an asynchronous operation eventually calls complete or completeExceptionally. The idea came from Javascript, which is indeed asynchronous but isn't multi-threaded.

1283822
  • 1,832
  • 17
  • 13
  • The function supplied to `thenApply` may also run on any thread that calls an arbitrary other, unrelated [“completion” method on that future](https://stackoverflow.com/a/46062939/2711488)… My favorite would be a thread trying to `cancel` the future… – Holger Jul 27 '18 at 16:08
  • Yes, understandably, the JSR's loose description on thread/execution order is intentional and leaves room for the Java implementers to freely do what they see fit. The take away is they promise to run it somewhere eventually, under something you do not control. If you want control, use the `Async` variants. But that is beside the point, because the OP is clearly struggling to understand asynchrony, which is orthogonal to thread management. I had to call out the difference between the two in my answer. OP should understand asynchony first before worrying about thread management in any way. – 1283822 May 19 '21 at 21:59
  • 1
    while thenApplyAsync either uses a default Executor (a.k.a. thread pool), <---- do you know which default Thread Pool is that? @1283822 – Sam YC Jun 29 '21 at 03:02
  • The default executor is promised to be a separate thread pool. Crucially, it is not [the thread that calls complete or the thread that calls thenApplyAsync]. If you compile your code against the OpenJDK libraries, the answer is in the [OpenJDK Sources](https://github.com/openjdk/jdk/blob/739769c8fc4b496f08a92225a12d07414537b6c0/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java#L2612), which is most of the time `ForkJoinPool.commonPool()`. – 1283822 Jul 12 '21 at 16:16
2

In both thenApplyAsync and thenApply the Consumer<? super T> action passed to these methods will be called asynchronously and will not block the thread that specified the consumers.

The difference have to do with which thread will be responsible for calling the method Consumer#accept(T t):

Consider an AsyncHttpClient call as below: Notice the thread names printed below. I hope it give you clarity on the difference:

// running in the main method
// public static void main(String[] args) ....

CompletableFuture<Response> future =
    asyncHttpClient.prepareGet(uri).execute().toCompletableFuture();

log.info("Current Thread " + Thread.currentThread().getName());

//Prints "Current Thread main"

thenApply Will use the same thread that completed the future.

//will use the dispatcher threads from the asyncHttpClient to call `consumer.apply`
//The thread that completed the future will be blocked by the execution of the method `Consumer#accept(T t)`.
future.thenApply(myResult -> {
    log.info("Applier Thread " + Thread.currentThread().getName());
    return myResult;
})

//Prints: "Applier Thread httpclient-dispatch-8"

thenApplyAsync Will use the a thread from the Executor pool.

//will use the threads from the CommonPool to call `consumer.accept`
//The thread that completed the future WON'T be blocked by the execution of the method `Consumer#accept(T t)`.
future.thenApplyAsync(myResult -> {
    log.info("Applier Thread " + Thread.currentThread().getName());
    return myResult;
})

//Prints: "Applier Thread ForkJoinPool.commonPool-worker-7"

future.get() Will block the main thread .

//If called, `.get()` may block the main thread if the CompletableFuture is not completed.
future.get();

Conclusion

The Async suffix in the method thenApplyAsync means that the thread completing the future will not be blocked by the execution of the Consumer#accept(T t) method.

The usage of thenApplyAsync vs thenApply depends if you want to block the thread completing the future or not.

DLopes
  • 567
  • 2
  • 6
  • 13
1

This is what the documentation says about CompletableFuture's thenApplyAsync:

Returns a new CompletionStage that, when this stage completes normally, is executed using this stage's default asynchronous execution facility, with this stage's result as the argument to the supplied function.

So, thenApplyAsync has to wait for the previous thenApplyAsync's result:

In your case you first do the synchronous work and then the asynchronous one. So, it does not matter that the second one is asynchronous because it is started only after the synchrounous work has finished.

Let's switch it up. In some cases "async result: 2" will be printed first and in some cases "sync result: 2" will be printed first. Here it makes a difference because both call 1 and 2 can run asynchronously, call 1 on a separate thread and call 2 on some other thread, which might be the main thread.

CompletableFuture<Integer> future
                = CompletableFuture.supplyAsync(() -> 0);

future.thenApplyAsync(x -> x + 1) // call 1
                .thenApplyAsync(x -> x + 1)
                .thenAccept(x -> System.out.println("async result: " + x));

future.thenApply(x -> x + 1) // call 2
                .thenApply(x -> x + 1)
                .thenAccept(x -> System.out.println("sync result:" + x));
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
  • 3
    Whether "call 2" executes on the main thread or some other thread is dependant on the state of `future`. If it already has a result then it will execute on the main thread. Otherwise it will register a callback and execute when the result of `supplyAsync` is available on whichever thread that might be. – Kiskae Nov 25 '17 at 19:19
1

The second step (i.e. computation) will always be executed after the first step.

If the second step has to wait for the result of the first step then what is the point of Async?

Async means in this case that you are guaranteed that the method will return quickly and the computation will be executed in a different thread.

When calling thenApply (without async), then you have no such guarantee. In this case the computation may be executed synchronously i.e. in the same thread that calls thenApply if the CompletableFuture is already completed by the time the method is called. But the computation may also be executed asynchronously by the thread that completes the future or some other thread that calls a method on the same CompletableFuture. This answer: https://stackoverflow.com/a/46062939/1235217 explained in detail what thenApply does and does not guarantee.

So when should you use thenApply and when thenApplyAsync? I use the following rule of thumb:

  • non-async: only if the task is very small and non-blocking, because in this case we don't care which of the possible threads executes it
  • async (often with an explicit executor as parameter): for all other tasks
Stefan Feuerhahn
  • 1,564
  • 1
  • 14
  • 22
0
  • Sync APIs: If results are readily available, it will use current main thread or it will use preceding task thread. We cannot provide executor service, it will use preceding task executor service.
  • Async APIs: Task always run on different thread than current running thread. We can also provide our own executor service based on IO or CPU intensive task.
Gautam Tadigoppula
  • 932
  • 11
  • 13