Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions fork-join/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
title: "Fork/Join Pattern in Java: Parallel Divide-and-Conquer Processing"
shortTitle: Fork/Join
description: "Learn the Fork/Join design pattern in Java with real-world examples, class diagrams, and code samples. Understand how to split large tasks into parallel subtasks for improved performance."
category: Concurrency
language: en
tag:
- Performance
- Scalability
- Concurrency
---

## Also known as

* Divide and Conquer Parallelism
* Work-Stealing Parallelism

## Intent of Fork/Join Design Pattern

The Fork/Join pattern recursively splits a large task into independent subtasks (fork),
processes them in parallel across multiple threads, and combines their results (join) to
produce a final outcome. It maximizes CPU utilization for computationally intensive problems.

## Detailed Explanation of Fork/Join Pattern with Real-World Examples

Real-world example

> Imagine a large warehouse that needs to count all its inventory items across 100 aisles.
> Instead of one person counting every aisle sequentially, the manager divides the warehouse
> into sections and assigns a team of workers to count each section simultaneously. Once
> every section is counted, the manager collects all partial counts and sums them into the
> total inventory. This is the Fork/Join pattern: split the work, do it in parallel, merge
> the results.

In plain words

> Fork/Join splits a big problem into smaller pieces, solves each piece in parallel on
> separate threads, then combines all results back together.

## Programmatic Example of Fork/Join Pattern in Java

We demonstrate the pattern by computing the sum of a large array in parallel using Java's
built-in `ForkJoinPool` and `RecursiveTask`.

The `SumTask` is a recursive task that splits the array when it's too large:

```java
public class SumTask extends RecursiveTask<Long> {

private static final int THRESHOLD = 1000;
private final long[] numbers;
private final int start;
private final int end;

@Override
protected Long compute() {
int length = end - start;

if (length <= THRESHOLD) {
// Base case: sum directly
long sum = 0;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}

// Fork: split into two halves
int mid = start + length / 2;
SumTask leftTask = new SumTask(numbers, start, mid);
SumTask rightTask = new SumTask(numbers, mid, end);

leftTask.fork(); // run left half asynchronously
long rightResult = rightTask.compute(); // compute right half here
long leftResult = leftTask.join(); // wait for left half

// Join: combine results
return leftResult + rightResult;
}
}
```

The `ForkJoinSumCalculator` provides a clean API:

```java
public class ForkJoinSumCalculator {

private final ForkJoinPool pool;

public ForkJoinSumCalculator() {
this.pool = ForkJoinPool.commonPool();
}

public long calculateSum(long[] numbers) {
SumTask task = new SumTask(numbers, 0, numbers.length);
return pool.invoke(task);
}
}
```

Running the example in `App`:

```java
long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
long result = calculator.calculateSum(numbers);
System.out.println("Fork/Join sum: " + result);
```

Program output:

```
Fork/Join sum: 50000005000000
Expected sum: 50000005000000
Correct: true
Time taken: 45 ms
Available processors: 8
```

## When to Use the Fork/Join Pattern in Java

* When you have a large, CPU-intensive task that can be divided into independent subtasks.
* When the subtasks are roughly the same size and don't depend on each other.
* When you want to utilize multiple CPU cores without manually managing threads.
* When the problem naturally fits a divide-and-conquer strategy (e.g., sorting, searching,
numerical computation).

## When NOT to Use Fork/Join

* For I/O-bound tasks (network calls, file reads) — use virtual threads or async I/O instead.
* When subtasks are too small — the overhead of forking exceeds the benefit.
* When tasks have dependencies on each other and cannot run independently.

## Benefits and Trade-offs of Fork/Join Pattern

Benefits:

* Maximizes CPU utilization through work-stealing algorithm.
* Scales automatically with the number of available processors.
* Built into Java's standard library (`java.util.concurrent`) — no external dependencies.
* Clean recursive decomposition makes the code readable and maintainable.

Trade-offs:

* Overhead from task creation and thread management for very small problems.
* Requires tasks to be independent — shared mutable state introduces bugs.
* Choosing an appropriate threshold requires tuning for optimal performance.
* Debugging parallel code is inherently harder than sequential code.

## Related Java Design Patterns

* [Divide and Conquer](https://java-design-patterns.com/patterns/divide-and-conquer/):
Fork/Join is the parallel execution variant of the classic divide-and-conquer strategy.
* [Thread Pool](https://java-design-patterns.com/patterns/thread-pool/): Fork/Join uses a
specialized pool with work-stealing semantics.

## References

* [Java Documentation for ForkJoinPool](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ForkJoinPool.html)
* [Java Concurrency in Practice — Brian Goetz](https://amzn.to/4aRMruW)
* [Java Documentation for RecursiveTask](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/RecursiveTask.html)
50 changes: 50 additions & 0 deletions fork-join/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

This project is licensed under the terms of the MIT license, see LICENSE.md in the root
of this project's GitHub repository for the full license text.

Copyright © 2014-2024 Ilkka Seppälä

-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>

<artifactId>fork-join</artifactId>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.forkjoin.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
45 changes: 45 additions & 0 deletions fork-join/src/main/java/com/iluwatar/forkjoin/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.iluwatar.forkjoin;

import java.util.stream.LongStream;

/**
* The Fork/Join pattern is a concurrency design pattern that splits a large task into smaller
* subtasks (fork), processes them in parallel, and then combines the results (join).
*
* In Java, this pattern is implemented using {@link java.util.concurrent.ForkJoinPool} and
* {@link java.util.concurrent.RecursiveTask}. Worker threads in the pool use a work-stealing
* algorithm — idle threads take tasks from busy threads — maximizing CPU utilization.
*
* In this example, we demonstrate the pattern by computing the sum of a large array in
* parallel. The array is recursively split in half until each piece is small enough to sum
* directly, then results are combined back up.
*/
public final class App {

private App() {
}

/**
*
* @param args command line arguments, not used
*/
public static void main(String[] args) {
// Create an array of 10 million numbers: [1, 2, 3, ..., 10_000_000]
long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();

// Calculate sum using Fork/Join
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
long startTime = System.currentTimeMillis();
long result = calculator.calculateSum(numbers);
long endTime = System.currentTimeMillis();

// The expected sum of 1 to N is N*(N+1)/2
long expected = 10_000_000L * 10_000_001L / 2;

System.out.println("Fork/Join sum: " + result);
System.out.println("Expected sum: " + expected);
System.out.println("Correct: " + (result == expected));
System.out.println("Time taken: " + (endTime - startTime) + " ms");
System.out.println("Available processors: " + Runtime.getRuntime().availableProcessors());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.iluwatar.forkjoin;

import java.util.concurrent.ForkJoinPool;

/**
* ForkJoinSumCalculator provides a convenient API to sum an array of numbers using
* the Fork/Join framework. It creates a {@link ForkJoinPool}, submits a {@link SumTask},
* and returns the computed sum.
*
* The pool manages a set of worker threads that process subtasks in parallel.
* Idle threads can "steal" work from busy threads, maximizing CPU utilization.
*
*/
public class ForkJoinSumCalculator {

private final ForkJoinPool pool;

/**
* Creates a calculator using the common ForkJoinPool (uses all available CPU cores).
*/
public ForkJoinSumCalculator() {
this.pool = ForkJoinPool.commonPool();
}

/**
* Creates a calculator with a specific number of threads.
*
* @param parallelism the number of worker threads to use
*/
public ForkJoinSumCalculator(int parallelism) {
this.pool = new ForkJoinPool(parallelism);
}

/**
* Calculates the sum of all elements in the array using Fork/Join parallelism.
*
* @param numbers the array of numbers to sum
* @return the total sum of all elements
*/
public long calculateSum(long[] numbers) {
if (numbers == null || numbers.length == 0) {
return 0;
}
SumTask task = new SumTask(numbers, 0, numbers.length);
return pool.invoke(task);
}
}
Loading
Loading