Fork/Join框架
Fork/Join 是 Java 7 引入的并行计算框架,适合把大任务递归拆分成小任务并行执行,再合并结果。它常用于分治算法和并行流底层执行。
# 1. 分治模型
大任务
├─ 子任务 A
│ ├─ A1
│ └─ A2
└─ 子任务 B
├─ B1
└─ B2
fork 拆分 -> 并行执行 -> join 合并
适合任务:
- 可递归拆分。
- 子任务相互独立。
- 合并成本可控。
- CPU 密集型为主。
# 2. 核心类
| 类 | 作用 |
|---|---|
ForkJoinPool | 执行 ForkJoin 任务的线程池 |
RecursiveTask<V> | 有返回值任务 |
RecursiveAction | 无返回值任务 |
ForkJoinTask | 任务基类 |
# 3. RecursiveTask 示例
public class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000;
private final long[] array;
private final int start;
private final int end;
public SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
int mid = (start + end) >>> 1;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork();
long rightResult = right.compute();
long leftResult = left.join();
return leftResult + rightResult;
}
}
执行:
ForkJoinPool pool = new ForkJoinPool();
Long result = pool.invoke(new SumTask(array, 0, array.length));
# 4. 工作窃取
每个工作线程维护自己的双端队列。
Worker A deque: [A1, A2, A3]
Worker B deque: []
Worker B 空闲
│
▼
从 Worker A 队列尾部窃取任务
工作窃取减少线程空闲,提高 CPU 利用率。
# 5. 阈值选择
任务拆得太大:并行度不足。任务拆得太小:调度开销过高。
总时间 = 计算时间 + 拆分成本 + 调度成本 + 合并成本
阈值需要压测决定,不存在通用值。
# 6. 与普通线程池区别
| 对比项 | ForkJoinPool | ThreadPoolExecutor |
|---|---|---|
| 任务模型 | 递归拆分和合并 | 独立任务队列 |
| 调度 | 工作窃取 | 工作队列 |
| 适合 | 分治计算 | 通用异步任务 |
| 阻塞任务 | 不适合长阻塞 | 可按场景配置 |
ForkJoinPool 不适合大量阻塞 IO 任务。
# 7. 并行流关系
parallelStream() 默认使用公共 ForkJoinPool。
list.parallelStream()
.map(this::calculate)
.toList();
注意:
- 公共池被阻塞会影响其他并行流。
- 不要在并行流里做长时间阻塞 IO。
- 不要修改共享可变状态。
# 专家实践与边界
ForkJoin 适合把大任务递归拆成小任务,再利用工作窃取提升 CPU 利用率。它不是通用异步线程池,更不适合大量阻塞 IO。
大任务
├─ 子任务 A
│ ├─ A1
│ └─ A2
└─ 子任务 B
├─ B1
└─ B2
工作线程空闲时从其他队列尾部窃取任务
| 场景 | 是否适合 | 原因 |
|---|---|---|
| 大数组并行计算 | 适合 | 可拆分、CPU 密集 |
| 递归搜索 | 适合 | 子任务结构自然 |
| 批量 RPC | 不适合 | 阻塞 IO 占用工作线程 |
| 小任务过多 | 谨慎 | 拆分和调度成本可能超过收益 |
| 有共享写状态 | 谨慎 | 合并结果要设计清楚 |
任务拆分粒度是性能关键。拆得太粗并行度不够,拆得太细调度成本高。实际项目要通过阈值和压测调整。
# Tips 快问快答
Q:Fork/Join 适合什么任务? A:可拆分、可合并、子任务独立的 CPU 密集型任务。
Q:RecursiveTask 和 RecursiveAction 区别? A:前者有返回值,后者无返回值。
Q:工作窃取是什么? A:空闲线程从其他线程任务队列中偷任务执行。
Q:阈值怎么设置? A:通过压测平衡拆分开销和并行收益。
Q:ForkJoinPool 适合阻塞 IO 吗? A:通常不适合。
Q:parallelStream 用的是什么线程池? A:默认使用公共 ForkJoinPool。
Q:并行流一定更快吗? A:不一定,任务太小或有阻塞时可能更慢。
Q:fork 后一定要 join 吗? A:需要获取结果或确保完成时要 join,否则任务结果无法合并。
上次更新: 2026/06/25, 14:19:18