How to Run Tasks in Parallel with ExecutorService and CompletableFuture in Java
Let’s explore how we can take advantage of asynchronous operations from ExecutorService
and CompletableFuture
to run tasks in parallel.
1. Create ExecutorService
Let’s first create a new thread pool that reuses some fixed number of threads.
ExecutorService executorService = Executors.newFixedThreadPool(10);
Of course, depending on our use case, we can use other thread pool implementations:
ExecutorService::newCachedThreadPool
ExecutorService::newSingleThreadExecutor
ExecutorService::newWorkStealingPool
ExecutorService::unconfigurableExecutorService
2. Generate CompletableFuture
list (async tasks)
Next, we’ll create a list of CompletableFuture
objects and append some tasks that we want to run asynchronously.
List<CompletableFuture> futures = new ArrayList();
for (/* Iterate through stuff to do */) {
futures.add(doStuffAsync());
}
Here’s an example of a task that returns a CompletableFuture
.
CompletableFuture<Void> doStuffAsync() {
return CompletableFuture.runAsync(() -> doStuff(), executorService)
}
Some common methods to generate a CompletableFuture
include:
CompletableFuture::runAsync
, takes no inputs and returnsCompletableFuture<Void>
(no result)CompletableFuture::supplyAsync
, takes no input and returnsCompletableFuture<U>
(result of typeU
)
Here, we’re using runAsync()
to execute some action (i.e. doStuff()
) asynchronously with no inputs or outputs.
3. Execute async tasks with CompletableFuture::allOf
We can use CompletableFuture::allOf
to run all of the tasks in parallel. It returns a new CompletableFuture
that completes when all of the CompletableFuture
tasks are complete.
CompletableFuture
.allOf(futures.toArray(CompletableFuture[]::new))
.thenRun(() -> System.out.println("Done"));
Note that if any of the tasks complete with an exception, the returned CompletableFuture
will as well.