Expert · Multithreading and concurrency · topic 43 of 60 · 30 min
Executors, futures and CompletableFuture
Explanation
Why thread pools?
Creating a thread costs time and memory. A server that started a new platform thread for every request would collapse under load. A thread pool keeps a set of worker threads alive and feeds them tasks from a queue. The Executors class creates common pools.
ExecutorService pool = Executors.newFixedThreadPool(4); // 4 reusable worker threads
Future<Double> gst = pool.submit(() -> calculateGst(orders)); // a Callable returns a value
Future<Integer> stock = pool.submit(() -> countStock(warehouse));
double tax = gst.get(); // waits if not done yet (throws InterruptedException, ExecutionException)
int units = stock.get(2, TimeUnit.SECONDS); // wait at most 2 s, else TimeoutException
pool.shutdown(); // accept no new tasks; finish queued ones
pool.awaitTermination(30, TimeUnit.SECONDS);Runnable is a task with no result (void run()); Callable<V> returns a value and may throw checked exceptions (V call()). If the task throws, Future.get throws ExecutionException with the original as its cause.
CompletableFuture: chaining async steps
Future.get() blocks the calling thread. CompletableFuture (Java 8) lets you describe what should happen when results arrive, combine several results, and handle errors, all without blocking until the very end.
CompletableFuture<Customer> customer = CompletableFuture.supplyAsync(() -> crm.find(id));
CompletableFuture<List<Order>> orders = CompletableFuture.supplyAsync(() -> orderRepo.forCustomer(id));
CompletableFuture<String> summary = customer
.thenCombine(orders, (c, os) -> c.name() + " has " + os.size() + " orders") // when BOTH are done
.thenApply(String::toUpperCase) // transform
.exceptionally(ex -> "Summary unavailable: " + ex.getMessage()); // on any failure
System.out.println(summary.join()); // join() waits; like get() but throws unchecked exceptions
CompletableFuture<Void> all = CompletableFuture.allOf(customer, orders); // wait for many
CompletableFuture<String> withTimeout = summary.orTimeout(2, TimeUnit.SECONDS); // Java 9+| Method | Does |
|---|---|
| supplyAsync(supplier) | Run a task that returns a value on a pool (the common ForkJoinPool by default) |
| thenApply(fn) | Transform the result |
| thenCompose(fn) | Chain another async step that returns a CompletableFuture |
| thenCombine(other, fn) | Combine two independent results |
| exceptionally / handle | Recover from errors |
| allOf / anyOf | Wait for all / the first of many |
| join() / get() | Wait for the final value |
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Executors, futures and CompletableFuture · 1/6
In this lesson you will run tasks with thread pools and futures.
Prefer a video by a teacher? Find free videos on “java executorservice future completablefuture tutorial” on YouTube ↗
In real life: Loading a travel search page
A travel site's search API calls three partner services (trains, buses, flights) for the same route. Calling them one after another takes the sum of their times; launching them as CompletableFutures and combining the results with allOf takes only as long as the slowest, with a timeout so one slow partner cannot hold up the page.
var trains = CompletableFuture.supplyAsync(() -> trainApi.search(route)).orTimeout(3, TimeUnit.SECONDS);
var buses = CompletableFuture.supplyAsync(() -> busApi.search(route)).orTimeout(3, TimeUnit.SECONDS);
CompletableFuture.allOf(trains, buses)
.exceptionally(ex -> null) // keep going even if one partner fails
.join();
List<Option> results = new ArrayList<>();
if (!trains.isCompletedExceptionally()) results.addAll(trains.join());
if (!buses.isCompletedExceptionally()) results.addAll(buses.join());Practice (0/3 done)
A task submitted to an ExecutorService throws an exception. What does future.get() do?
The same "combine two async results" idea in JavaScript, which runs here (promises are JavaScript's CompletableFuture): write summary(findCustomer, findOrders, id) that starts both async calls at the same time and returns "<NAME IN CAPITALS> has <n> orders", or "Summary unavailable" if either fails.
Write a program that uses an ExecutorService with 3 threads (in try-with-resources, Java 19+, or with shutdown) to compute the total sales of 5 shops in parallel (each task sleeps 500 ms to simulate a database call), collects the 5 Futures, and prints the grand total and the time taken (about 1 second, not 2.5).
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Atomic variables and concurrent collectionsMultithreading and concurrency · same module
- Futures and async/awaitFlutter and Dart · Async Dart and packages
- Concurrency 2: atomics, async, futures and condition variablesC++ Programming (modern C++ and DSA) · Exceptions, concurrency and building projects with CMake
- Async Rust with tokioRust Programming · Concurrency and async
- async and await: waiting without blockingC# and .NET · Exceptions, delegates, events and async
- Why async, and callbacksJavaScript Programming (language in depth) · Asynchronous JavaScript
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the difference between Runnable and Callable?
Answer: Callable returns a value and can throw checked exceptions
V call() throws Exception vs void run().
What happens if you never call shutdown() on a fixed thread pool in main?
Answer: The JVM may keep running because the pool threads are alive
Pool threads are non-daemon.
Which CompletableFuture method combines two independent results?
Answer: thenCombine
thenCombine runs a function when both are complete.
What does exceptionally do?
Answer: Provides a fallback value when an earlier stage failed
It recovers from errors in the chain.
Where does an error from an awaited Future appear?Related
Answer: At the await, where try/catch can handle it
It is rethrown at the await.
Two bank balances must be updated together by several threads. Which tool?Related
Answer: One std::mutex locked around both updates
Only a lock makes the combined change appear all at once.
From Concurrency 2: atomics, async, futures and condition variables
What is the danger of a long CPU loop inside an async task?Related
Answer: It blocks the worker thread, so other tasks stall
Tasks must yield at .await points; use spawn_blocking for heavy work.
Why avoid task.Result on an unfinished task?Related
Answer: It blocks the thread and can deadlock in some apps
Blocking defeats the purpose of async and can deadlock.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Multithreading and concurrency” before its exam: Atomic variables and concurrent collections, Virtual threads (Java 21).
- Module 9 exam: Multithreading and concurrency · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, JDBC, Maven, Gradle and JUnit. 60 topics left in the course before the final exam.