Java: splitting work across a thread pool

Divide a sum into chunks, run them on an ExecutorService, and combine the Future results.

Java
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);
  }
}
Output
sum of squares 1..1000 = 333833500

Explanation

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.

Explore the dry run
Guided walkthroughReady
1import java.util.*;
2import java.util.concurrent.*;
3
4class Main {
5 public static void main(String[] args) throws Exception {
6 ExecutorService pool = Executors.newFixedThreadPool(4);
7 List<Future<Long>> parts = new ArrayList<>();
8 for (int start = 1; start <= 1000; start += 250) {
9 final int from = start;
10 parts.add(pool.submit(() -> {
11 long sum = 0;
12 for (int n = from; n < from + 250; n++) {
13 sum += (long) n * n;
14 }
15 return sum;
16 }));
17 }
18 long total = 0;
19 for (Future<Long> part : parts) {
20 total += part.get();
21 }
22 pool.shutdown();
23 System.out.println("sum of squares 1..1000 = " + total);
24 }
25}

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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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

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.

Further reading