The labs on this site need JavaScript to run. The same content is written out below.
Learn › Java › Threads and concurrency
Sequential and parallel calls
Several external calls: one after another, or all at once?
To build a single product detail page, the application calls several services. If it calls them one after another, all the times add up. If it calls them at the same time, the page takes only as long as the slowest call.
Checked against real execution · Checked by running code on JDK 21
Concepts in this lab
- CompletableFuture
- This object represents the result of an asynchronous task. You can attach actions that run next when the task finishes, and you can combine the results of several asynchronous tasks. The flow is built in the style of "when it finishes, do this", so no thread is stopped while waiting for the result.
- Synchronous and asynchronous
- Synchronous means that the side that requests a task waits until it receives the result and only then moves on. Asynchronous means that the requester starts the task and moves on to the next work without waiting for the result, and the result is delivered later through a callback, a Future, or a similar means.
- Thread pool
- This structure keeps a fixed number of threads and reuses them, so that threads are not created and destroyed every time they are needed. It reduces the cost of creating threads, and by limiting the number of threads that run at the same time, it prevents excessive memory use and context switching. Tasks to be processed pile up in a queue, and an idle thread takes one and runs it. Tomcat's request handling and a DB connection pool follow the same idea.
- ForkJoinPool and the common pool
- This is a thread pool used to split a large task into small tasks, process them on several threads, and combine the results. The JVM has a common pool (commonPool) that everything shares, and parallel streams run in it by default. If you run a long task there, other tasks that use the same pool also slow down.
Try these
- Default settings — The 4 services are called one after another. All the times add up.
- Switch to parallel — The page takes only as long as the single slowest call.
- Parallel, but with only 2 threads — Only 2 calls are in progress at a time.
- A timeout on the slow call — A call that takes longer than 1s is not waited for. A default value is used in its place.
- 5 calls, sequential — Each additional call makes the page slower by the time of that call.
Running with the default settings
- 0.0s: The call to Product details starts (a call that takes 0.3s).
- 0.3s: The call to Product details receives its response.
- 0.3s: The call to Stock starts (a call that takes 0.5s).
- 0.8s: The call to Stock receives its response.
- 0.8s: The call to Reviews starts (a call that takes 0.8s).
- 1.6s: The call to Reviews receives its response.
- 1.6s: The call to Recommendations starts (a call that takes 1.6s).
- 3.2s: The call to Recommendations receives its response.
- 3.2s: All the results have arrived. The application assembles the page and responds.
Result of this run
- With sequential calls, the response time is the sum of all the call times. If the calls do not depend on each other's results, there is no reason to make them one after another.
Code
var product = productApi.get(id);
var stock = stockApi.get(id);
var reviews = reviewApi.list(id);
var recommend = recommendApi.list(id);
return new ProductPage(product, stock, reviews, recommend);
Assumptions of this model
The time each call takes (product details 0.3s, stock 0.5s, reviews 0.8s, recommendations 1.6s, coupons 0.4s) is a fixed example, and the model assumes that the calls do not need each other's results. If the result of one call is the input of the next, those two must be made sequentially. For parallel calls, the model assumes blocking calls, in which one thread makes one call and waits for it. completeOnTimeout is a method that has existed since JDK 9. This was checked by actually running code on JDK 21 on this PC. With 4 calls of the same durations, the measured times were 3,243ms sequential, 1,603ms parallel (5 threads), 2,127ms parallel (2 threads), and 1,011ms with the timeout of 1s applied, which matched the values of the model (3.2s, 1.6s, 2.1s, 1.0s).
Previous lab: Thread count and context switching · Next lab: Blocking, non-blocking, and virtual threads