1. The Hook (The "Byte-Sized" Intro)
In a Nutshell: Fork/Join Framework implements divide-and-conquer parallelism—split large task into smaller subtasks (fork), process concurrently, combine results (join). ForkJoinPool uses work-stealing (idle threads steal tasks from busy threads). Extend RecursiveTask (returns value) or RecursiveAction (void). Parallel streams use Fork/Join internally! Ideal for recursive algorithms (merge sort, tree traversal). Not for blocking I/O!
Think of pizza delivery. One driver can't deliver 100 orders. Fork = split into 10 drivers × 10 orders each. Join = collect all delivery confirmations. Work steal = idle driver helps busy driver!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy: The Team Project
- Fork: Manager splits project into sub-tasks, assigns to team members
- Join: Manager waits for all sub-tasks to complete, combines results
- Work stealing: Free teammate helps overloaded teammate
Fork/Join Flow
3. Technical Mastery (The "Deep Dive")
RecursiveTask Example
class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000;
private int[] array;
private int start, end;
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// Base case: compute directly
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
// Recursive case: split
int mid = (start + end) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork(); // Async execute left
long rightResult = right.compute(); // Sync execute right
long leftResult = left.join(); // Wait for left
return leftResult + rightResult;
}
}
}4. Interactive & Applied Code
import java.util.concurrent.*;
import java.util.stream.*;
public class ForkJoinDemo {
public static void main(String[] args) {
demonstrateSumArray();
demonstrateParallelStreams();
}
static void demonstrateSumArray() {
int[] array = IntStream.rangeClosed(1, 10000).toArray();
ForkJoinPool pool = new ForkJoinPool();
SumTask task = new SumTask(array, 0, array.length);
long startTime = System.nanoTime();
Long result = pool.invoke(task);
long endTime = System.nanoTime();
System.out.println("Sum: " + result);
System.out.println("Time: " + (endTime - startTime) / 1_000_000 + "ms");
}
static void demonstrateParallelStreams() {
System.out.println("\n=== PARALLEL STREAMS ===");
// Sequential stream
long sequential = IntStream.rangeClosed(1, 1_000_000)
.sum();
System.out.println("Sequential: " + sequential);
// Parallel stream (uses ForkJoinPool.commonPool())
long parallel = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.sum();
System.out.println("Parallel: " + parallel);
}
}
// SumTask implementation
class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000;
private int[] array;
private int start, end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
if (length <= THRESHOLD) {
// Base case: sequential computation
return computeDirectly();
}
// Recursive case: split into subtasks
int mid = start + length / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
// Fork left task
leftTask.fork();
// Compute right task in current thread
Long rightResult = rightTask.compute();
// Join left task
Long leftResult = leftTask.join();
return leftResult + rightResult;
}
private Long computeDirectly() {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
}
// RecursiveAction example (no return value)
class PrintTask extends RecursiveAction {
private static final int THRESHOLD = 10;
private int start, end;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
for (int i = start; i < end; i++) {
System.out.println("Processing: " + i);
}
} else {
int mid = (start + end) / 2;
invokeAll(
new PrintTask(start, mid),
new PrintTask(mid, end)
);
}
}
}5. The Comparison & Decision Layer
| Feature | ExecutorService | Fork/Join |
|---|---|---|
| Task type | Independent tasks | Subtask hierarchy |
| Work distribution | Static | Work stealing |
| Use case | I/O, web requests | CPU-intensive recursion |
| Overhead | Low | Higher (fork/join) |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Why should you call fork() on one subtask and compute() on the other?" Answer: Optimize thread usage—avoid creating unnecessary threads!
// ❌ SUBOPTIMAL: Both fork()
left.fork();
right.fork();
long leftRes = left.join();
long rightRes = right.join();
// Current thread sits idle waiting!
// ✅ OPTIMAL: One fork(), one compute()
left.fork();
long rightRes = right.compute(); // Use current thread!
long leftRes = left.join();
// Current thread does work instead of sitting idleBest practice: Fork all except one, compute the last one!
Pro-Tip: When NOT to use Fork/Join:
// ❌ BAD: Blocking I/O (wastes threads waiting)
class FileReadTask extends RecursiveTask<String> {
protected String compute() {
return readFile(); // Blocks thread!
}
}
// ✅ GOOD: CPU-intensive computation
class ComputeTask extends RecursiveTask<Double> {
protected Double compute() {
return complexCalculation(); // Pure CPU work
}
}Fork/Join designed for CPU-bound tasks only!