Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In which thread do CompletableFuture's completion handlers execute?

I have a question about CompletableFuture method:

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

The thing is the JavaDoc says just this:

Returns a new CompletionStage that, when this stage completes normally, is executed with this stage's result as the argument to the supplied function. See the CompletionStage documentation for rules covering exceptional completion.

What about threading? In which thread is this going to be executed? What if the future is completed by a thread pool?

like image 978
St.Antario Avatar asked Sep 05 '17 17:09

St.Antario


People also ask

How does CompletableFuture work?

What is CompletableFuture? A CompltableFuture is used for asynchronous programming. Asynchronous programming means writing non-blocking code. It runs a task on a separate thread than the main application thread and notifies the main thread about its progress, completion or failure.

What is completion stage in Java?

A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A stage completes upon termination of its computation, but this may in turn trigger other dependent stages.

When use CompletableFuture?

CompletableFuture is used for asynchronous programming in Java. Asynchronous programming is a means of writing non-blocking code by running a task on a separate thread than the main application thread and notifying the main thread about its progress, completion or failure.

What is CompletableFuture in java 8?

Java 8 introduced the CompletableFuture class. CompletableFuture is at the same time a building block and a framework, with about 50 different methods for composing, combining, and executing asynchronous computation steps and handling errors.


2 Answers

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 aren't supported by the documentation. Thus, I think it's worthwhile to pick it apart. Specifically, we should read this paragraph very carefully:

Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture, or by any other caller of a completion method.

Sounds straightforward enough, but it's light on details. It seemingly deliberately avoids describing when a dependent completion may be invoked on the completing thread versus during a call to a completion method like thenApply. As written, the paragraph above is practically begging us to fill in the gaps with assumptions. That's dangerous, especially when the topic concerns concurrent and asynchronous programming, where many of the expectations we've developed as programmers get turned on their head. Let's take a careful look at what the documentation doesn't say.

The documentation does not claim that dependent completions registered before a call to complete() will run on the completing thread. Moreover, while it states that a dependent completion might be invoked when calling a completion method like thenApply, it does not state that a completion will be invoked on the thread that registers it (note the words "any other").

These are potentially important points for anyone using CompletableFuture to schedule and compose tasks. Consider this sequence of events:

  1. Thread A registers a dependent completion via f.thenApply(c1).
  2. Some time later, Thread B calls f.complete().
  3. Around the same time, Thread C registers another dependent completion via f.thenApply(c2).

Conceptually, complete() does two things: it publishes the result of the future, and then it attempts to invoke dependent completions. Now, what happens if Thread C runs after the result value is posted, but before Thread B gets around to invoking c1? Depending on the implementation, Thread C may see that f has completed, and it may then invoke c1 and c2. Alternatively, Thread C may invoke c2 while leaving Thread B to invoke c1. The documentation does not rule out either possibility. With that in mind, here are assumptions that are not supported by the documentation:

  1. That a dependent completion c registered on f prior to completion will be invoked during the call to f.complete();
  2. That c will have run to completion by the time f.complete() returns;
  3. That dependent completions will be invoked in any particular order (e.g., order of registration);
  4. That dependent completions registered before f completes will be invoked before completions registered after f completes.

Consider another example:

  1. Thread A calls f.complete();
  2. Some time later, Thread B registers a completion via f.thenApply(c1);
  3. Around the same time, Thread C registers a separate completion via f.thenApply(c2).

If it is known that f has already run to completion, one might be tempted to assume that c1 will be invoked during f.thenApply(c1) and that c2 will be invoked during f.thenApply(c2). One might further assume that c1 will have run to completion by the time f.thenApply(c1) returns. However, the documentation does not support these assumptions. It may be possible that one of the threads calling thenApply ends up invoking both c1 and c2, while the other thread invokes neither.

A careful analysis of the JDK code could determine how the hypothetical scenarios above might play out. But even that is risky, because you may end up relying on an implementation detail that is (1) not portable, or (2) subject to change. Your best bet is not to assume anything that's not spelled out in the javadocs or the original JSR spec.

tldr: Be careful what you assume, and when you write documentation, be as clear and deliberate as possible. While brevity is a wonderful thing, be wary of the human tendency to fill in the gaps.

like image 61
Mike Strobel Avatar answered Sep 22 '22 13:09

Mike Strobel


The policies as specified in the CompletableFuture docs could help you understand better:

  • Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture, or by any other caller of a completion method.

  • All async methods without an explicit Executor argument are performed using the ForkJoinPool.commonPool() (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run each task). To simplify monitoring, debugging, and tracking, all generated asynchronous tasks are instances of the marker interface CompletableFuture.AsynchronousCompletionTask.

Update: I would also advice on reading this answer by @Mike as an interesting analysis further into the details of the documentation.

like image 30
Naman Avatar answered Sep 21 '22 13:09

Naman