Java: parallel streams and CompletableFuture
Count items with a parallel stream and combine two asynchronous results.
On this page
Java streams and lambdas.
- Count items with a parallel stream and combine two asynchronous results.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A parallel stream splits its data across the common fork/join pool and combines the partial results. It suits independent, stateless operations such as filtering and counting, where the answer does not depend on the order in which pieces finish.
CompletableFuture models a value that will be available later. supplyAsync starts work on another thread, and thenCombine joins two independent futures once both complete. This is the building block for asynchronous pipelines, such as calling two services at once.
Code example
import java.util.concurrent.*;
import java.util.stream.*;
class Main {
public static void main(String[] args) throws Exception {
long evens = LongStream.rangeClosed(1, 1_000_000).parallel().filter(n -> n % 2 == 0).count();
System.out.println("evens: " + evens);
CompletableFuture<Integer> price = CompletableFuture.supplyAsync(() -> 40);
CompletableFuture<Integer> tax = CompletableFuture.supplyAsync(() -> 2);
int total = price.thenCombine(tax, Integer::sum).get();
System.out.println("total: " + total);
}
}evens: 500000 total: 42
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
import java.util.concurrent.*;import java.util.stream.*; class Main { public static void main(String[] args) throws Exception { long evens = LongStream.rangeClosed(1, 1_000_000).parallel().filter(n -> n % 2 == 0).count(); System.out.println("evens: " + evens); CompletableFuture<Integer> price = CompletableFuture.supplyAsync(() -> 40); CompletableFuture<Integer> tax = CompletableFuture.supplyAsync(() -> 2); int total = price.thenCombine(tax, Integer::sum).get(); System.out.println("total: " + total); }}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| The range is split into parts, filtered on several threads, and the counts are added. | evens = 500000 | — | |
| Counting is order-independent, so the result is always the same. | evens = 500000 | evens: 500000 | |
| Two independent computations start on pool threads. | price and tax are running | evens: 500000 | |
| When both finish, their results are added; get waits for the answer. | 40 + 2 = 42 | evens: 500000 | |
| Print the combined result. | 40 + 2 = 42 | evens: 500000 total: 42 |
Behind the scenes
How Java processes this code
The Java compiler produces bytecode. The JVM loads the class and invokes its main method.
Cost and common mistakes
O(n) work for the stream, divided across the available cores; the asynchronous part is O(1).
- Do not modify shared non-thread-safe collections from inside a parallel stream.
- Parallel streams help only for large data and CPU-bound work. For small inputs they are often slower.
- Blocking tasks in the common pool can starve other parallel work.
Try it yourself
How would you run the two futures on your own executor?
Reveal answer and reasoning
Pass the executor as a second argument: CompletableFuture.supplyAsync(() -> 40, executor).