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::newCachedThreadPoolExecutorService::newSingleThreadExecutorExecutorService::newWorkStealingPoolExecutorService::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.