ddreian
ddreian

Reputation: 1856

When do CompletableFutures in JDK8 block the execution threads?

Example 1:

CompletableFuture
    .supplyAsync(new MySupplier())
    .someCompletableFutureMethod(new SomeCompletableFutureComsumer())

Does the ForkJoin thread ever get blocked?

Example 2:

final CompletableFuture cf = new CompletableFuture();
cf = executor.execute(new Runnable() {
    public void run() {
        //do work
        cf.complete(result);
    }
});
cf.whenComplete(new MyConsumer());

Do any of the treads involved ever get blocked?

( I know, I should use Callable instead of Runnable :)

Is there any way I can misuse the API without using the method inherited from Future to block any thread (main, ForkJoin, executor)?

Assuming I am not using any blocking APIs (I know future.get() blocks).

Upvotes: 1

Views: 6308

Answers (3)

Didier L
Didier L

Reputation: 20608

All those methods will need some form of synchronization, which cannot be implemented properly without ever blocking.

For instance:

  1. supplyAsync() tries to queue a job in the common ForkJoin pool, through its execute() method. This method relies on an Unsafe and awaitRunStateLock() to queue the job;
  2. a similar thing applies to your executor.execute(), though your implementation might be different;
  3. concerning someCompletableFutureMethod() (and whenComplete()):
    1. except for the *Async() methods, it will first need to check whether this future is already completed: if that's the case, the passed-in function will be executed on the calling thread, which you might consider as blocking (though it is actually executing your code);
    2. otherwise, the task must be queued, which relies on loops and an Unsafe to do this – see the CompletableFuture.*push*(*) methods.
  4. cf.complete() will need to take care of the child tasks, such as the one sent to whenComplete():
    1. it will need some lock to make sure those tasks are executed only once;
    2. it will execute immediately all the non-asynchronous tasks (like in 3.1);
    3. it will need to schedule the asynchronous tasks (like in 1. and 2.)
  5. Most CompletableFuture methods that accept a lambda as parameter (and return a new CompletableFuture) actually hide a complete() call (on the returned future) that will be executed by the same thread that executed the lambda, hence blocking it like in 4.¹

Of course, except in a highly concurrent environment where many threads are trying to push and execute tasks at the same time, you will probably not notice this blocking. Cases 3.1 and 4.2 are the ones you are the most likely to encounter as they occur quite often (if you use the non-*Async() methods).

¹ There is a subtle exception with the thenCompose() method as the thread that executes the complete() call will depend on whether the CompletionStage returned by the lambda is already completed or not. Additionally, the non-static *Async() methods seem to reuse the executor for this call, so another thread could be used.

Upvotes: 3

GhostCat
GhostCat

Reputation: 140613

See the javadoc for get():

Waits if necessary for this future to complete, and then returns its result.

In other words: when you call "blocking" methods on a CompletableFuture, it should block. Otherwise, it will not.

No method in that javadoc has a description that reads: might randomly block ;-) !

Upvotes: 4

kewne
kewne

Reputation: 2298

CompletableFuture adds the join() method, which is kind of a non-checked exception version of Future.get() (docs here). I don't recommend using it though because, by not taking a timeout, it can hang the thread and you can almost always rewrite the code using thenApply and friends. I try to enforce this by always using CompletionStage, which CompletableFuture implements.

I don't think any other methods except the ones inherited from Future block.

Upvotes: 4

Related Questions