Java: splitting work across a thread pool
Divide a sum into chunks, run them on an ExecutorService, and combine the Future results.
On this page
Java threads and lambdas.
- Divide a sum into chunks, run them on an ExecutorService, and combine the Future results.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
Data parallelism splits one large job into independent pieces that run at the same time and then combines the results. In Java, an ExecutorService manages a pool of worker threads. submit takes a task and returns a Future, which is a handle for a result that will exist later.
The program sums the squares of 1 to 1000 in four chunks of 250. Each task returns its partial sum, and calling get on each Future waits for that result. Because the chunks do not share mutable state, no locks are needed. shutdown lets the pool stop once the tasks are done.
Code example
import java.util.*;
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(4);
List<Future<Long>> parts = new ArrayList<>();
for (int start = 1; start <= 1000; start += 250) {
final int from = start;
parts.add(pool.submit(() -> {
long sum = 0;
for (int n = from; n < from + 250; n++) {
sum += (long) n * n;
}
return sum;
}));
}
long total = 0;
for (Future<Long> part : parts) {
total += part.get();
}
pool.shutdown();
System.out.println("sum of squares 1..1000 = " + total);
}
}sum of squares 1..1000 = 333833500
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
import java.util.*;import java.util.concurrent.*; class Main { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newFixedThreadPool(4); List<Future<Long>> parts = new ArrayList<>(); for (int start = 1; start <= 1000; start += 250) { final int from = start; parts.add(pool.submit(() -> { long sum = 0; for (int n = from; n < from + 250; n++) { sum += (long) n * n; } return sum; })); } long total = 0; for (Future<Long> part : parts) { total += part.get(); } pool.shutdown(); System.out.println("sum of squares 1..1000 = " + 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 |
|---|---|---|---|
| Create a pool of four worker threads. | pool size = 4 | — | |
| Each loop pass submits one chunk of 250 numbers and keeps its Future. | 4 tasks submitted | — | |
| get waits until that chunk is finished, then adds its partial sum. | partial sums are added in order | — | |
| The result matches the formula n(n+1)(2n+1)/6. | total = 333833500 | sum of squares 1..1000 = 333833500 |
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) total work; with p threads and equal chunks the ideal time is O(n / p) plus coordination overhead.
- Forgetting shutdown leaves non-daemon worker threads alive, so the program may never exit.
- Splitting work too finely makes task overhead larger than the work itself.
- get rethrows task failures as ExecutionException, so handle it in real code.
Try it yourself
What happens if the pool has one thread?
Reveal answer and reasoning
The tasks run one after another and the answer is identical, so there is no speed-up but no change in correctness.