Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which executor is used when composing Java CompletableFutures?

I have a method on some repository class that returns a CompletableFuture. The code that completes these futures uses a third party library which blocks. I intend to have a separate bounded Executor which this repository class will use to make these blocking calls.

Here is an example:

public class PersonRepository {
    private Executor executor = ...
    public CompletableFuture<Void> create(Person person) {...}
    public CompletableFuture<Boolean> delete(Person person) {...}
}

The rest of my application will compose these futures and do some other things with the results. When these other functions that are supplied to thenAccept, thenCompose, whenComplete, etc, I don't want them to run on the Executor for the repository.

Another example:

public CompletableFuture<?> replacePerson(Person person) {
    final PersonRepository repo = getPersonRepository();
    return repo.delete(person)
        .thenAccept(existed -> {
            if (existed) System.out.println("Person deleted"));
            else System.out.println("Person did not exist"));
        })
        .thenCompose(unused -> {
            System.out.println("Creating person");
            return repo.create(person);
        })
        .whenComplete((unused, ex) -> {
            if (ex != null) System.out.println("Creating person");
            repo.close();
        });
}

The JavaDoc states:

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.

Side question: Why is there an or here? In what case is there another caller of a completion method that does not complete the current future?

Main question: If I want all the println to be executed by a different Executor than the one used by the repository, which methods do I need to make async and provide the executor manually?

Obviously the thenAccept needs to be changed to thenAcceptAsync but I'm not sure about that point onwards.

Alternative question: Which thread completes the returned future from thenCompose?

My guess is that it will be whatever thread completes the future returned from the function argument. In other words I would need to also change whenComplete to whenCompleteAsync.

Perhaps I am over complicating things but this feels like it could get quite tricky. I need to pay a lot of attention to where all these futures come from. Also from a design point of view, if I return a future, how do I prevent callers from using my executor? It feels like it breaks encapsulation. I know that all the transformation functions in Scala take an implicit ExecutionContext which seems to solve all these problems.

like image 850
steinybot Avatar asked Mar 09 '17 21:03

steinybot


People also ask

What is Executor in CompletableFuture?

Overview. defaultExecutor() is an instance method of the CompletableFuture class. This method is used to get the default pool of threads that are used by the async methods of the CompletableFuture class when a custom executor is not specified. The defaultExecutor method is defined in the CompletableFuture class.

How does CompletableFuture work in Java?

Instead of catching an exception in a syntactic block, the CompletableFuture class allows us to handle it in a special handle method. This method receives two parameters: a result of a computation (if it finished successfully), and the exception thrown (if some computation step did not complete normally).

Where is CompletableFuture used?

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.


2 Answers

Side Question: If you assigned the intermediate CompletionStage to a variable and call a method on it, it would get executed on the same thread.

Main Question: Only the first one, so change thenAccept to thenAcceptAsync -- all the following ones will execute their steps on the thread that is used for the accept.

Alternative Question: the thread that completed the future from thenCompose is the same one as was used for the compose.

You should think of the CompletionStages as steps, that are executed in rapid succession on the same thread (by just applying the functions in order), unless you specifically want the step to be executed on a different thread, using async. All next steps are done on that new thread then.

In your current setup the steps would be executed like this:

Thread-1: delete, accept, compose, complete

With the first accept async, it becomes:

Thread-1: delete
Thread-2: accept, compose, complete

As for your last question, about the same thread being used by your callers if they add additional steps -- I don't think there is much you can do about aside from not returning a CompletableFuture, but a normal Future.

like image 167
john16384 Avatar answered Sep 28 '22 06:09

john16384


Just from my empirical observations while playing around with it, the thread that executes these non-async methods will depend on which happens first, thenCompose itself or the task behind the Future.

If thenCompose completes first (which, in your case, is almost certain), then the method will run on the same thread that is executing the Future task.

If the task behind your Future completes first, then the method will run immediately on the calling thread (i.e. no executor at all).

like image 20
Joe C Avatar answered Sep 28 '22 07:09

Joe C