Does completableFuture in Java 8 scale to multiple cores?

13,978

Solution 1

CompletableFuture represents a task which is associated with some Executor. If you did not specify executor explicitly (for example, you used CompletableFuture.supplyAsync(Supplier) instead of CompletableFuture.supplyAsync(Supplier, Executor)), then common ForkJoinPool is used as executor. This pool could be obtained via ForkJoinPool.commonPool() and by default it creates as many threads as many hardware threads your system has (usually number of cores, double it if your cores support hyperthreading). So in general, yes, if you use all defaults, then multiple cores will be used for your completable futures.

Solution 2

CompletableFuture itself is not scheduled to a thread (or core). Tasks are. To achieve parallelism, you need to create multiple tasks. If your methods which return CompletableFuture submit tasks like

return CompletableFuture.supplyAsync(this::calculate);

then multiple tasks are started. If they just create CompletableFuture like

return new CompletableFuture();

then no tasks are started and no parallelism present.

CompletableFuture objects created by CompletableFuture{handle, thenCombine, thenCompose, thenApply} are not connected to parallel tasks, so parallelism is not increased.

CompletableFuture objects created by CompletableFuture{handleAsync, thenCombineAsync, thenComposeAsync, thenApplyAsync} are connected to parallel tasks, but these tasks are executed strictly after the task corresponding to the this CompletableFuture object, so cannot increase parallelism.

Solution 3

Having a bunch of CompletableFutures doesn't tell you anything about how they will be completed.

There are two kind of completions:

  • Explicit, through cancel, complete, completeExceptionally, obtrudeException and obtrudeValue on an instance, or by obtainng a future with the completedFuture static method

  • Implicit, through executing a provided function, whether it returns normally or exceptionally, or through the completion of a previous future

    For instance:

    • exceptionally completes normally without running the provided function if the previous future completes normally

    • Every other chaining method, except for handle and whenComplete and their *Async variations, complete exceptionally without running the provided function if the previous future, or any of the previous futures in the combining (*Both*, *Combine* and *Either*) methods, complete exceptionally

    • Otherwise, the future completes when the provided function runs and completes either normally or exceptionally

If the futures you have were created without a function or they're not chained to another future, or in other words, if they don't have a function associated, then they will only complete explicitly, and as such it makes no sense to say if this kind of completable future runs, much less if they may use multiple threads.

On the other hand, if the futures have a function, it depends on how they were created:

  • If they're all independent and use the ForkJoinPool.commonPool() (or a cached thread pool or similar) as the executor, then they will probably run in parallel, possibly using as many active threads as the number of cores

  • If they all have a dependency on each other (except for one) or if the executor is single-threaded, then they'll run one at a time

  • Anything in between is valid, such as:

    • some futures may depend on each other, or on some other internal future you have no knowledge of

    • some futures may have been created with e.g. a fixed thread pool executor where you'll see a limited degree of concurrently running tasks

Invoking join does not tell a future to start running, it just waits for it to complete.

So, to finally answer your question:

  • If the future has a function associated, then it may already be running, it may or may not run its function depending on how it was chained and the completion of the previous future, and it may never run if it doesn't have a function or if it was completed before it had a chance to run its function

  • The futures that are already running or that will run do so:

    • On the provided executor when chained with the *Async methods or when created with the *Async static methods that take an executor

    • On the ForkJoinPool.commonPool() when chained with the *Async methods or when created with the *Async static methods that don't take an executor

    • On the same thread as where the future they depend on is completed when chained without the *Async methods in case the future is not yet complete

    • On the current thread if the future they depend on is already completed when chained without the *Async methods


In my opinion, the explicit completion methods should have been segregated to a e.g. CompletionSource interface and have a e.g. CompletableFutureSource class that implements it and provides a future, much like .NET's relation between a TaskCompletionSource and its Task.

As things are now, most probably you can tamper with the completable futures you have by completing them in a way not originally intended. For this reason, you should not use a CompletableFuture after you expose it publicly; it's your API user's CompletableFuture from then on.

Share:
13,978
user1870400
Author by

user1870400

Updated on July 29, 2022

Comments

  • user1870400
    user1870400 almost 2 years

    Lets say I have a single thread that calls bunch of methods that return completablefuture and say I add all of them to a list and in the end I do completablefutures.allof(list_size).join(). Now does the futures in the list can scale to multiple cores? other words are the futures scheduled into multiple cores to take advantage of parallelism?

  • user1870400
    user1870400 almost 8 years
    Why -1? This makes sense to me as long as it true?
  • Sotirios Delimanolis
    Sotirios Delimanolis almost 8 years
    A CompletableFuture is not associated with any Executor. Those are just helpful methods for scheduling work on some thread that will eventually complete a CompletableFuture. The future itself is unrelated to the work or to the thread(s) that complete(s) it. Don't frame it this way.
  • user1870400
    user1870400 almost 8 years
    @SotiriosDelimanolis so if I just use the way I specified in my question then its all scheduled on a single thread ? and doesn't use multiple cores? please confirm even though it might be a redundant question
  • user1870400
    user1870400 almost 8 years
    @SotiriosDelimanolis so if I just use the way I specified in my question then its all scheduled on a single thread ? and doesn't get scheduled on multiple cores? please confirm even though it might be a redundant question
  • Sotirios Delimanolis
    Sotirios Delimanolis almost 8 years
    @user1870400 There is no extra work scheduled. The futures in list_size have a dependent stage. All threads that are working on completing those futures have a chance of touching the returned CompletableFuture. Additionally, your calling thread gets potentially blocked on the join.
  • user1870400
    user1870400 almost 8 years
    @SotiriosDelimanolis you still don't seem to answer my question which is does all those futures gets scheduled on multiple threads and potentially multiple cores to take advantage of parallelism? Yes or No?
  • Sotirios Delimanolis
    Sotirios Delimanolis almost 8 years
    @user1870400 Your question is based on an incorrect assumption and therefore doesn't have a yes/no answer. A CompletableFuture doesn't get scheduled.
  • user1870400
    user1870400 almost 8 years
    @SotiriosDelimanolis you are just picking on my terminology. I am not sure how to explain it in your terms. I wrap some piece of code inside a future now does those pieces of code from all different futures gets scheduled on multiple threads while taking advantage of parallelism ? Yes or No?
  • user1870400
    user1870400 almost 8 years
    Hi! the completable futures in my list call CompletableFuture.handle, CompletableFuture.thenCompose, CompletableFuture.thenApply. and all these are called by single thread so even in this case you seem to suggest that these will be executed in parallel? am I right?
  • a better oliver
    a better oliver almost 8 years
    @user1870400 "I wrap some piece of code inside a future" No, a CompletableFuture is not a wrapper for code. There's code that creates the future, code that completes / cancels the future and code that waits for the result wrapped by the future. What you are most likely referring to is the code that completes the future. As long as you do not tell us how this code is invoked nobody can reliably answer your question.
  • a better oliver
    a better oliver almost 8 years
    @user1870400 Original Javadoc: "A {@code Future} represents the result of an asynchronous computation." Note: The result, not the computation. Whether or not the computations run on different threads depends on you, i.e. on how you invoke them.
  • Didier L
    Didier L almost 8 years
    @user1870400, the CompletableFuture methods you are referring to are all instance methods, so you must have created those instances in the first place. The parallelism will only depend on how you create them.