Lesson Completion
Back to course

Fork/Join Framework: Divide and Conquer Parallelism

Advanced
25 minutes4.9Java

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

graph TD A[Large Task] -->|fork| B[Subtask 1] A -->|fork| C[Subtask 2] B -->|fork| D[Sub-subtask 1.1] B -->|fork| E[Sub-subtask 1.2] D -->|compute| F[Result 1.1] E -->|compute| G[Result 1.2] C -->|compute| H[Result 2] F -->|join| B G -->|join| B B -->|join| A H -->|join| A A --> I[Final Result]

3. Technical Mastery (The "Deep Dive")

RecursiveTask Example

java
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

java
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

FeatureExecutorServiceFork/Join
Task typeIndependent tasksSubtask hierarchy
Work distributionStaticWork stealing
Use caseI/O, web requestsCPU-intensive recursion
OverheadLowHigher (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!

java
// ❌ 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 idle

Best practice: Fork all except one, compute the last one!

Pro-Tip: When NOT to use Fork/Join:

java
// ❌ 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!

Topics Covered

Java FundamentalsConcurrency

Tags

#java#multithreading#threads#concurrency#parallelism

Last Updated

2025-02-01