In which thread are the CompletableFuture completion handlers executed?

I have a question about the CompletableFuture method:

public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) 

The fact is that JavaDoc only says the following:

Returns a new CompletionStage, which, when this stage is completed, is usually executed with the result of this stage as an argument the supplied function. See the CompletionStage documentation for rules covering exceptional completion.

What about threads? In which thread will this be done? What if the future is completed by the thread pool?

+11
source share
4 answers

The policies specified in CompletableFuture may help you better understand:

  • The actions provided for dependent refinements of non-asynchronous methods can be performed by a thread that completes the current CompletableFuture, or by any other caller of the completion method.

  • All asynchronous methods without an explicit argument to Executor are executed using ForkJoinPool.commonPool() (if it does not support parallelism of at least two levels, in which case a new thread will be created to complete each task). To simplify monitoring, debugging, and tracking, all generated asynchronous tasks are instance instances of the CompletableFuture.AsynchronousCompletionTask interface marker.

Update . I also advise reading this answer from @Mike as an interesting analysis further in the details of the documentation.

+14
source

As @nullpointer points out , the documentation tells you what you need to know. However, the relevant text is surprisingly vague, and some of the comments (and answers) posted here seem to rely on assumptions that are not supported by the documentation. Thus, I consider it appropriate to highlight it. In particular, we should carefully read this paragraph:

The actions provided for dependent terminations of non-asynchronous methods can be performed by a thread that completes the current CompletableFuture or by any other caller of the termination method.

It sounds simple enough, but it highlights the details. Apparently, he deliberately avoids describing when a dependent termination can be called in a terminating thread or while invoking a termination method such as thenApply . As written, the above paragraph will actually ask us to fill in the blanks with assumptions. This is dangerous, especially when it comes to parallel and asynchronous programming, where many of the expectations that we have developed when programmers find themselves on their head. Let's take a closer look at what says .

The documentation does not state that dependent refinements registered before calling complete() will be executed in the final chain. Moreover, while it states that dependent termination can be caused by calling a termination method such as thenApply , it does not indicate that termination will be called in the thread that registers it (note the words β€œany other”) .

These are potentially important points for those who use CompletableFuture to plan and write tasks. Consider this sequence of events:

  • Thread A registers dependent termination via f.thenApply(c1) .
  • After a while, Thread B calls f.complete() .
  • Around the same time, Thread C registers another dependent termination through f.thenApply(c2) .

Conceptually, complete() does two things: it publishes the result of the future, and then it tries to invoke dependent refinements. Now, what happens if Thread C starts after publishing the result value, but before Thread B comes close to calling c1 ? Depending on the implementation, Thread C can see that f complete, and it can then call c1 and c2 . Alternatively, Thread C can call c2 , leaving Thread B to call c1 . The documentation does not exclude the possibility. Keeping this in mind, here are the assumptions that are not supported by the documentation:

  • The completion of the dependent completion of c , registered on f before completion, will be called during the call to f.complete() ;
  • That c will be completed by the time f.complete() returns;
  • These dependent additions will be called in any particular order (for example, registration procedure);
  • All dependent completions registered before completion of f completed, before completions are completed after completion of f .

Consider another example:

  • Thread A calls f.complete() ;
  • After a while, Thread B registers completion through f.thenApply(c1) ;
  • Around the same time, Thread C registers a single termination through f.thenApply(c2) .

If it is known that f has already been completed, it may be tempting to assume that c1 will be called during f.thenApply(c1) and that c2 will be called during f.thenApply(c2) . It can also be assumed that c1 will be completed by the time f.thenApply(c1) returns. However, the documentation does not support these assumptions. It is possible that one of the threads calling thenApply terminates the call to both c1 and c2 , while the other thread does not call anyone.

A thorough analysis of the JDK code could determine how hypothetical scenarios can be illustrated above. But even this is risky because you can rely on implementation details that are (1) not portable, or (2) subject to change. It is best not to assume anything that is not specified in javadocs or the original JSR specification.

TL; DR: Be careful what you expect, and when you write documentation, be as clear and thoughtful as possible. Although brevity is a wonderful thing, be careful with the human tendency to fill in the blanks.

+15
source

From Javadoc :

The actions provided for dependent terminations of non-asynchronous methods can be performed by a thread that completes the current CompletableFuture or by any other caller of the termination method.

More specific:

  • fn will be executed during the call to complete() in the context of the thread calling complete() .

  • If complete() already complete at the time that thenApply() called, fn will execute in the context of the thread that calls thenApply() .

+6
source

When it comes to streaming, there is no API documentation. Understanding how threads and futures work requires a bit of output. Start with one assumption: CompletableFuture does not spawn new threads on its own. Work will continue within existing flows.

thenApply will be launched on the CompletableFuture source thread. This is either a thread that calls complete() , or one that calls thenApply() if the future is already complete. If you want to control the flow is a good idea, if fn is a slow operation & mdash, then you should use thenApplyAsync .

+3
source

Source: https://habr.com/ru/post/1276250/


All Articles