ForkJoin Framework
Divide and conquer large tasks in parallel using ForkJoinPool, RecursiveTask, and parallel streams.
Overview
Key Points
- •ForkJoinPool.commonPool() is the shared pool used by parallel streams and default async tasks
- •RecursiveTask<V>: override compute() to return a value
- •RecursiveAction: override compute() for side-effect tasks with no return value
- •fork() schedules a subtask asynchronously on the pool
- •join() waits for the subtask and retrieves its result
- •invoke(task) submits a task and blocks until complete (entry point)
- •invokeAll(task1, task2) forks both and waits for both
- •Threshold controls when to stop splitting; too small wastes overhead, too large reduces parallelism
- •Do not block inside ForkJoin tasks; use ManagedBlocker for blocking I/O
Examples
Parallel sum with RecursiveTask
import java.util.concurrent.*;
public class ParallelSum extends RecursiveTask<Long> {
private static final int THRESHOLD = 10_000;
private final int[] arr;
private final int lo, hi;
ParallelSum(int[] arr, int lo, int hi) {
this.arr = arr; this.lo = lo; this.hi = hi;
}
@Override
protected Long compute() {
if (hi - lo <= THRESHOLD) {
long sum = 0;
for (int i = lo; i < hi; i++) sum += arr[i];
return sum;
}
int mid = (lo + hi) / 2;
ParallelSum left = new ParallelSum(arr, lo, mid);
ParallelSum right = new ParallelSum(arr, mid, hi);
left.fork(); // schedule left asynchronously
long rightResult = right.compute(); // compute right on this thread
long leftResult = left.join(); // wait for left
return leftResult + rightResult;
}
public static void main(String[] args) {
int[] arr = new int[1_000_000];
for (int i = 0; i < arr.length; i++) arr[i] = i + 1;
ForkJoinPool pool = ForkJoinPool.commonPool();
long sum = pool.invoke(new ParallelSum(arr, 0, arr.length));
System.out.println("Sum: " + sum);
}
}The pattern is: fork the left half, compute the right half on the current thread (saves thread-switch overhead), then join the left. This is the standard fork-compute-join idiom for binary splits.
Parallel array sorting action with RecursiveAction
import java.util.Arrays;
import java.util.concurrent.*;
public class ParallelSort extends RecursiveAction {
private static final int THRESHOLD = 5_000;
private final int[] arr;
private final int lo, hi;
ParallelSort(int[] arr, int lo, int hi) {
this.arr = arr; this.lo = lo; this.hi = hi;
}
@Override
protected void compute() {
if (hi - lo <= THRESHOLD) {
Arrays.sort(arr, lo, hi);
return;
}
int mid = (lo + hi) / 2;
invokeAll(
new ParallelSort(arr, lo, mid),
new ParallelSort(arr, mid, hi)
);
// merge two sorted halves
int[] tmp = Arrays.copyOfRange(arr, lo, hi);
int i = 0, j = mid - lo, k = lo;
while (i < mid - lo && j < hi - lo)
arr[k++] = (tmp[i] <= tmp[j]) ? tmp[i++] : tmp[j++];
while (i < mid - lo) arr[k++] = tmp[i++];
while (j < hi - lo) arr[k++] = tmp[j++];
}
public static void main(String[] args) {
int[] arr = {5, 3, 8, 1, 9, 2, 7, 4, 6};
ForkJoinPool.commonPool().invoke(new ParallelSort(arr, 0, arr.length));
System.out.println(Arrays.toString(arr));
}
}invokeAll forks both subtasks and waits for both. RecursiveAction is for in-place modifications where no value is returned.
Common Mistakes
- Calling fork() on both subtasks before joining either. The pattern should be: fork one, compute the other on the current thread, then join the forked one. Forking both wastes the current thread by sitting idle during join.
- Setting the threshold too low. Each fork creates overhead for task creation and scheduling. If the threshold is 1, most time is spent managing tasks, not doing real work. Benchmark to find the sweet spot (typically thousands of elements).
- Performing blocking I/O inside a ForkJoin task. ForkJoin threads are precious; blocking them starves the pool. Use ManagedBlocker or move I/O outside the task.
Note
For most use cases, parallel streams (list.parallelStream()) are simpler than writing RecursiveTask manually. They use ForkJoinPool.commonPool() internally. Write RecursiveTask directly only when you need custom splitting logic or a non-stream computation.
Exercises
Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.
Completed: 0 / 2 exercises
Knowledge Check
Question 1 of 4