Definition
- ForkJoinPool – a specialized
ExecutorServiceoptimized for recursive divide-and-conquer tasks. - Fork – split a large task into smaller subtasks and submit them to the pool.
- Join – wait for subtask results and merge them.
- Work-stealing – each worker thread has a double-ended queue (deque); idle workers steal tasks from the bottom of busy workers' deques.
- RecursiveTask
– a task that returns a result; override compute()to implement the fork/join logic. - RecursiveAction – a task that returns void.
- commonPool() – the JVM-wide shared instance with parallelism =
Runtime.availableProcessors() - 1.
Sorting 1 million account records at National Bank of India: fork splits the pile into two halves (500K each), splits again (250K each), until small enough. Each worker thread sorts their piece. Join merges sorted pieces back together. Work-stealing: Worker-1 finishes early and steals from Worker-3's pile (takes from the bottom so Worker-3 is not disturbed at the top). All workers stay busy.
Understanding the Concepts
What is divide-and-conquer with ForkJoinPool?
The pattern: if the task is small enough, compute directly (base case). Otherwise, split into subtasks, fork one, compute the other, and join results.
package com.bank.service;
import java.util.concurrent.*;
public class AccountSumTask extends RecursiveTask<Long> {
private final long[] balances;
private final int start, end;
private static final int THRESHOLD = 10_000;
public AccountSumTask(long[] balances, int start, int end) {
this.balances = balances;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// Base case: small enough, compute directly
long sum = 0;
for (int i = start; i < end; i++) sum += balances[i];
return sum;
}
// Split into two subtasks
int mid = (start + end) / 2;
AccountSumTask left = new AccountSumTask(balances, start, mid);
AccountSumTask right = new AccountSumTask(balances, mid, end);
left.fork(); // submit left to pool (async)
long rightResult = right.compute(); // compute right in current thread
long leftResult = left.join(); // wait for left's result
return leftResult + rightResult;
}
public static void main(String[] args) {
long[] balances = new long[1_000_000];
for (int i = 0; i < balances.length; i++) balances[i] = 100;
ForkJoinPool pool = new ForkJoinPool(4);
long total = pool.invoke(new AccountSumTask(balances, 0, balances.length));
System.out.println("Total: Rs." + total);
pool.shutdown();
}
}
Total: Rs.100000000
How does work-stealing work?
Each worker thread has a deque. The owner pushes and pops from the top. Thieves steal from the bottom. This minimizes contention.
Worker-1 deque: [task-A][task-B][task-C] ← owner works from TOP Worker-2 deque: [task-D] ← finishes quickly Worker-2 steals from Worker-1's BOTTOM: Worker-1 deque: [task-A][task-B] (task-C stolen) Worker-2 deque: [task-C] (stolen, now busy)
What is ForkJoinPool.commonPool()?
ForkJoinPool common = ForkJoinPool.commonPool();
System.out.println("Parallelism: " + common.getParallelism());
// cores - 1 (e.g., 3 on a 4-core machine)
Shared by: parallelStream(), CompletableFuture.supplyAsync(), Arrays.parallelSort().
How does parallelStream use ForkJoinPool?
long sum = LongStream.range(0, 1_000_000)
.parallel() // uses ForkJoinPool.commonPool()
.map(x -> x * x)
.sum();
// Internally: Spliterator splits data → ForkJoinPool workers process chunks → merge
What is RecursiveTask vs RecursiveAction?
| Class | Returns | Use case |
|---|---|---|
| RecursiveTask
| RecursiveAction | void | Sort in-place, process side effects |
Why should you never use ForkJoinPool for blocking IO?
ForkJoinPool assumes tasks complete quickly. Blocked threads cannot steal or be stolen from. Blocking all workers stalls every parallelStream and CompletableFuture in the JVM.
How do you run parallelStream on a custom pool?
ForkJoinPool custom = new ForkJoinPool(8);
List<String> results = custom.submit(() ->
accounts.parallelStream()
.map(this::generateStatement)
.toList()
).join();
// Runs on custom pool, not commonPool
What are common interview follow-up questions?
- What is the optimal threshold for splitting? Depends on task complexity. Profile. Common heuristic: array.length / (4 * parallelism).
- Can you use ForkJoinPool for non-recursive tasks? Yes, it works as a regular ExecutorService too.
- What is the relationship between fork() and compute()? fork() submits to the pool asynchronously; compute() runs synchronously in the current thread.
Visual Overview
Divide-and-Conquer with ForkJoinPool
SumTask(0..1M)
│
├── fork: SumTask(0..500K)
│ │
│ ├── fork: SumTask(0..250K)
│ └── compute: SumTask(250K..500K)
│
└── compute: SumTask(500K..1M)
│
├── fork: SumTask(500K..750K)
└── compute: SumTask(750K..1M)
Work-Stealing:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Worker-1 │ │ Worker-2 │ │ Worker-3 │ │ Worker-4 │
│ [A][B][C]│ │ [D] │ │ [E][F] │ │ [] │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
Worker-4 idle → steals C from Worker-1's bottom
Key Takeaway
ForkJoinPool is a specialized thread pool for divide-and-conquer parallelism. Tasks extend RecursiveTask (returns result) or RecursiveAction (void). In compute(): if the task is small enough, process directly. Otherwise, fork one subtask (submit to pool), compute the other (current thread), and join the forked result.
Work-stealing is the key optimization. Each worker has a deque. Idle workers steal from busy workers' deques, taking from the bottom while the owner works from the top. This minimizes contention and maximizes CPU utilization for CPU-bound tasks.
ForkJoinPool.commonPool() is the JVM-wide shared instance used by parallelStream() and CompletableFuture.supplyAsync(). It is designed for CPU-bound work. Never use it for blocking IO. At National Bank of India, VaR calculation on 10 million positions uses ForkJoinPool(8) with RecursiveTask: 50 seconds reduced to 7 seconds, a 7x speedup. Work-stealing keeps all 8 cores busy even with uneven position complexities.
Remember one sentence: ForkJoinPool splits tasks recursively and uses work-stealing so idle threads steal from busy ones.
---