本文回答什么问题 :ForkJoin 框架如何用"分治 + 工作窃取"让 1 亿级数组求和自动并行,覆盖 ForkJoinTask 三大子类、工作窃取 Deque 原理与 5 个可执行 Demo? 预计阅读时间 :25 分钟
源码版本 :OpenJDK 17(
java.util.concurrent.ForkJoinPool+ForkJoinTask+RecursiveTask)
JDK 7 引入的 ForkJoin 框架是 Java 并发库中最精妙的设计之一------Doug Lea 用"工作窃取算法"(Work-Stealing)让 CPU 密集型分治任务自动并行,而 JDK 8 的并行流和 CompletableFuture 默认线程池都构建在它之上。本文从"1 亿数组求和"的真实问题切入,用 5 个可执行 Demo + 4 张时序/类图,把工作窃取原理、RecursiveTask/RecursiveAction/CountedCompleter 三大子类一次性讲透。
核心要点速查(建议收藏)
- JDK 7 引入
ForkJoin框架,Doug Lea 设计,JDK 8 的parallelStream()底层直接使用ForkJoinPool.commonPool(),理解它是读懂并行流的钥匙。 - 核心算法:工作窃取(Work-Stealing) ------每个 Worker 线程持有自己的双端队列(Deque),自己从头部 pop(LIFO) ,空闲时从其他队列尾部 steal(FIFO) ,最大化缓存局部性 + 最小化锁竞争。
- 三大子类:
RecursiveTask<V>(有返回值)/RecursiveAction(无返回值)/CountedCompleter<T>(带完成回调) ------99% 场景用前两个,复杂依赖用第三个。 - ForkJoinPool 与 ThreadPoolExecutor 本质区别 :前者是"分治 + 窃取",每个线程私有队列;后者是"共享阻塞队列",IO 密集型必须用 ThreadPoolExecutor,CPU 密集型分治任务才用 ForkJoinPool。
commonPool()三大坑 :守护线程(主线程结束 JVM 退出) / 线程数 = CPU 核数 - 1(IO 密集不够用) / 被parallelStream共享(业务互相影响),生产环境必须自定义 ForkJoinPool。- fork/join 黄金模式:
fork() + compute() + join()------左任务fork()异步执行,右任务compute()同步执行(避免不必要窃取),最后join()阻塞等结果。任务执行时间 < 10ms 不要 fork,> 100ms 才值得 fork。
📋 概述
一句话:ForkJoin 框架是 Java 7 引入的"分治并行神器",通过"拆分-执行-合并"的递归模式 + "工作窃取"算法,让大任务自动分配到多个线程并行执行。
从一个真实问题切入
假设你有一个包含 1 亿个元素的数组,需要计算它们的总和。如果用单线程遍历,可能需要几秒甚至更长时间。如果直接创建 100 个线程,每个线程处理 100 万个元素,又会遇到线程管理的开销和负载不均衡的问题。
ForkJoin 解决的核心问题:
| 问题 | 没有 ForkJoin 时 | 有 ForkJoin 时 |
|---|---|---|
| 任务拆分 | 手动拆分,代码复杂 | 递归自动拆分,简洁优雅 |
| 负载均衡 | 固定分配,忙闲不均 | 工作窃取,自动均衡 |
| 线程复用 | 每个任务可能需要新线程 | 线程池复用,高效执行 |
| 结果合并 | 手动编写合并逻辑 | fork/join 自动合并 |

💡 生活类比:包工头互相借工人
理解 ForkJoin 最直观的方式是把它想象成建筑工地的施工队:

| 施工队概念 | ForkJoin 概念 | 类比说明 |
|---|---|---|
| 大工程 | 大任务 | 需要完成的整体工作 |
| 拆分工程 | Fork | 把大任务拆成多个小任务 |
| 等工人汇报 | Join | 等待所有子任务完成,汇总结果 |
| 借工人 | 工作窃取 | 干完活的工人去帮其他忙的工人 |
| 双端队列 | WorkQueue | 每个工人的任务清单 |
| 施工队 | ForkJoinPool | 管理工人的线程池 |
核心思想:
- Fork(拆分) :把大任务拆成小任务,直到小到可以直接计算
- Join(合并) :等所有子任务完成后,汇总结果
- 工作窃取:干完活的工人去"偷"其他工人队列尾部的任务,实现负载均衡
🔍 工作窃取算法 ⭐
工作窃取(Work Stealing)是 ForkJoin 框架的核心算法,也是它区别于普通线程池的关键。
为什么需要工作窃取?
传统线程池使用共享队列 ,所有线程从同一个队列取任务,存在锁竞争 。ForkJoin 让每个线程有自己的双端队列(Deque) ,只在自己队列上操作,减少竞争。

工作窃取的工作原理

关键特性:
- 双端队列(Deque) :线程从头部 取任务(LIFO,最近提交的先执行),窃取者从尾部偷任务(FIFO,最老的任务先执行)
- 减少竞争:每个线程主要操作自己的队列,只有窃取时才访问其他线程的队列
- 自动均衡:忙的线程不会被打扰,闲的线程主动帮忙
为什么从尾部窃取?
ini
Deque: [任务1, 任务2, 任务3, 任务4]
↑ ↑
线程自己取(LIFO) 窃取者取(FIFO)
- 自己取(LIFO) :最新创建的子任务通常是最小的,优先处理可以更快释放资源
- 窃取者取(FIFO) :最老的任务通常比较大,窃取者可以长时间工作,减少窃取频率
🔍 ForkJoinTask
ForkJoinTask 是 ForkJoin 框架的核心抽象类,代表一个可以被 Fork/Join 的任务。
类继承关系

RecursiveTask vs RecursiveAction
| 特性 | RecursiveTask | RecursiveAction |
|---|---|---|
| 返回值 | 有(V 类型) | 无(void) |
| compute() 返回 | 必须返回结果 | 不返回结果 |
| 典型场景 | 求和、排序、查找 | 批量处理、数据转换 |
| join() 获取 | 返回计算结果 | 返回 null |
| 示例 | 计算数组总和 | 给数组每个元素加 1 |
fork() 和 join() 方法
arduino
// fork():异步执行当前任务
// 将任务放入当前线程的双端队列,然后立即返回
// 调用者可以继续执行其他任务
// join():等待任务完成并获取结果
// 如果任务已完成,立即返回结果
// 如果任务未完成,阻塞当前线程直到任务完成
// 关键:join() 不会触发工作窃取(与 invoke() 不同)
// invoke():fork + join 的组合
// 异步执行任务,然后等待结果
// 可能触发工作窃取
scss
// 标准 fork/join 模式
MyTask left = new MyTask(data1);
MyTask right = new MyTask(data2);
left.fork(); // 异步执行左任务
Result rightResult = right.compute(); // 同步执行右任务
Result leftResult = left.join(); // 等待左任务完成
return merge(leftResult, rightResult);
🔍 ForkJoinPool
ForkJoinPool 是 ForkJoin 框架的线程池实现,与 ThreadPoolExecutor 有本质区别。
ForkJoinPool vs ThreadPoolExecutor
| 特性 | ForkJoinPool | ThreadPoolExecutor |
|---|---|---|
| 队列类型 | 每个线程自己的双端队列(Deque) | 共享的阻塞队列(BlockingQueue) |
| 任务调度 | 工作窃取(Work Stealing) | FIFO 或优先级调度 |
| 适用场景 | 递归可分的任务(分治算法) | 独立的异步任务 |
| 线程协作 | 线程间可以互相帮忙 | 线程独立工作 |
| 嵌套任务 | 支持 fork 子任务 | 不支持(会死锁) |
| 常用方法 | invoke()/submit() | execute()/submit() |
| 并行流 | parallelStream() 底层 | 不支持 |
ForkJoinPool 的两种模式
ini
// 1. 无返回值(RecursiveAction)
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new MyAction(data));
// 2. 有返回值(RecursiveTask)
ForkJoinPool pool = new ForkJoinPool();
Result result = pool.invoke(new MyTask(data));
// 3. 使用 commonPool()(全局共享)
ForkJoinPool commonPool = ForkJoinPool.commonPool();
commonPool() 的特点
csharp
// commonPool 是全局共享的 ForkJoinPool
// 特点:
// 1. 线程数 = CPU 核数 - 1(默认)
// 2. 是守护线程(daemon)
// 3. 被 parallelStream() 共享
// 4. 不适合 IO 密集型任务
// 获取 commonPool
ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println("commonPool 并行度: " + commonPool.getParallelism());
💻 可跑代码
Demo 1:RecursiveTask 实现数组求和
java
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinSumDemo extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000; // 阈值:小于此值直接计算
private final long[] array;
private final int start;
private final int end;
public ForkJoinSumDemo(long[] 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) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
// 拆分任务
int mid = start + length / 2;
ForkJoinSumDemo leftTask = new ForkJoinSumDemo(array, start, mid);
ForkJoinSumDemo rightTask = new ForkJoinSumDemo(array, mid, end);
// 异步执行左任务
leftTask.fork();
// 同步执行右任务
long rightResult = rightTask.compute();
// 等待左任务完成
long leftResult = leftTask.join();
// 合并结果
return leftResult + rightResult;
}
public static void main(String[] args) {
// 创建测试数据
long[] array = new long[10000];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
// 使用 ForkJoinPool 执行
ForkJoinPool pool = new ForkJoinPool();
long result = pool.invoke(new ForkJoinSumDemo(array, 0, array.length));
System.out.println("数组总和: " + result);
System.out.println("期望值: " + (10000L * 10001 / 2));
pool.shutdown();
}
}
// 预期输出:
// 数组总和: 50005000
// 期望值: 50005000
Demo 2:RecursiveAction 实现大数组并行处理
java
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.Arrays;
public class ForkJoinTransformDemo extends RecursiveAction {
private static final int THRESHOLD = 1000;
private final int[] array;
private final int start;
private final int end;
public ForkJoinTransformDemo(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected void compute() {
int length = end - start;
if (length <= THRESHOLD) {
// 小任务:给每个元素乘以 2
for (int i = start; i < end; i++) {
array[i] = array[i] * 2;
}
return;
}
// 拆分任务
int mid = start + length / 2;
ForkJoinTransformDemo leftTask = new ForkJoinTransformDemo(array, start, mid);
ForkJoinTransformDemo rightTask = new ForkJoinTransformDemo(array, mid, end);
invokeAll(leftTask, rightTask); // 同时执行两个任务
}
public static void main(String[] args) {
int[] array = new int[100];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
System.out.println("处理前: " + Arrays.toString(Arrays.copyOf(array, 10)));
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new ForkJoinTransformDemo(array, 0, array.length));
System.out.println("处理后: " + Arrays.toString(Arrays.copyOf(array, 10)));
pool.shutdown();
}
}
// 预期输出:
// 处理前: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 处理后: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Demo 3:ForkJoinPool 基本用法 + 工作窃取演示
java
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicInteger;
public class ForkJoinStealDemo extends RecursiveTask<Integer> {
private static final int THRESHOLD = 5;
private static final AtomicInteger taskCount = new AtomicInteger(0);
private final int n;
private final int depth;
public ForkJoinStealDemo(int n, int depth) {
this.n = n;
this.depth = depth;
}
@Override
protected Integer compute() {
int taskId = taskCount.incrementAndGet();
String threadName = Thread.currentThread().getName();
System.out.printf("[%s] 执行任务 #%d, n=%d, depth=%d%n",
threadName, taskId, n, depth);
if (n <= THRESHOLD) {
// 模拟耗时操作
try { Thread.sleep(100); } catch (InterruptedException e) {}
return n * n;
}
ForkJoinStealDemo task1 = new ForkJoinStealDemo(n - 1, depth + 1);
ForkJoinStealDemo task2 = new ForkJoinStealDemo(n - 2, depth + 1);
task1.fork(); // 异步执行
int result2 = task2.compute(); // 同步执行
int result1 = task1.join(); // 等待结果
return result1 + result2;
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4); // 4 个线程
System.out.println("ForkJoinPool 并行度: " + pool.getParallelism());
System.out.println("开始执行...\n");
long start = System.currentTimeMillis();
int result = pool.invoke(new ForkJoinStealDemo(20, 0));
long elapsed = System.currentTimeMillis() - start;
System.out.println("\n结果: " + result);
System.out.println("耗时: " + elapsed + "ms");
System.out.println("总任务数: " + taskCount.get());
pool.shutdown();
}
}
// 预期输出(线程名和任务顺序可能不同,体现工作窃取):
// ForkJoinPool 并行度: 4
// 开始执行...
// [ForkJoinPool-1-worker-1] 执行任务 #1, n=20, depth=0
// [ForkJoinPool-1-worker-1] 执行任务 #2, n=19, depth=1
// [ForkJoinPool-1-worker-2] 执行任务 #3, n=18, depth=2(工作窃取)
// ...
// 结果: 28656
// 耗时: 1234ms
// 总任务数: 39
Demo 4:parallelStream 底层使用 ForkJoinPool
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ParallelStreamDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// parallelStream 底层使用 ForkJoinPool.commonPool()
List<Integer> result = numbers.parallelStream()
.map(n -> {
System.out.println("[" + Thread.currentThread().getName() + "] 处理: " + n);
return n * n;
})
.sorted()
.collect(Collectors.toList());
System.out.println("结果: " + result);
// 注意:commonPool 的线程数 = CPU 核数 - 1
System.out.println("commonPool 并行度: " +
java.util.concurrent.ForkJoinPool.commonPool().getParallelism());
}
}
// 预期输出(顺序可能不同):
// [ForkJoinPool.commonPool-worker-1] 处理: 1
// [ForkJoinPool.commonPool-worker-1] 处理: 3
// [ForkJoinPool.commonPool-worker-2] 处理: 2
// ...
// 结果: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// commonPool 并行度: 7
Demo 5:自定义 ForkJoinPool
java
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;
public class CustomForkJoinPoolDemo extends RecursiveTask<Integer> {
private static final int THRESHOLD = 10;
private final int[] array;
private final int start;
private final int end;
public CustomForkJoinPoolDemo(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int length = end - start;
if (length <= THRESHOLD) {
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
int mid = start + length / 2;
CustomForkJoinPoolDemo left = new CustomForkJoinPoolDemo(array, start, mid);
CustomForkJoinPoolDemo right = new CustomForkJoinPoolDemo(array, mid, end);
left.fork();
int rightResult = right.compute();
int leftResult = left.join();
return leftResult + rightResult;
}
public static void main(String[] args) throws Exception {
int[] array = new int[1000];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
// 自定义 ForkJoinPool
ForkJoinPool pool = new ForkJoinPool(
4, // 并行度(线程数)
ForkJoinPool.defaultForkJoinWorkerThreadFactory, // 工作线程工厂
null, // 异常处理器
true // 是 asyncMode
);
// 监控线程池状态
System.out.println("并行度: " + pool.getParallelism());
System.out.println("活跃线程数: " + pool.getActiveThreadCount());
System.out.println("队列大小: " + pool.getQueuedTaskCount());
// 执行任务
int result = pool.invoke(new CustomForkJoinPoolDemo(array, 0, array.length));
System.out.println("结果: " + result);
// 等待线程池关闭
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("是否已终止: " + pool.isTerminated());
}
}
// 预期输出:
// 并行度: 4
// 活跃线程数: 0
// 队列大小: 0
// 结果: 500500
// 是否已终止: true
⚠️ 常见问题与踩坑
Q1:ForkJoinPool 和 ThreadPoolExecutor 怎么选?
答:根据任务特性选择:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 递归可分的任务 | ForkJoinPool | 分治算法 + 工作窃取天然适配 |
| 独立的异步任务 | ThreadPoolExecutor | 任务之间无依赖,共享队列更简单 |
| IO 密集型任务 | ThreadPoolExecutor | ForkJoinPool 不适合阻塞操作 |
| 并行流(parallelStream) | ForkJoinPool.commonPool() | 底层实现 |
| 需要自定义线程数 | 自定义 ForkJoinPool | commonPool 默认核数-1 |
scss
// ❌ 不适合 ForkJoin 的场景:IO 密集型
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new RecursiveTask() {
protected Object compute() {
// 发送 HTTP 请求(IO 操作,会阻塞线程)
httpClient.send(request); // ⚠️ 阻塞!
return result;
}
});
// ✅ IO 密集型用 ThreadPoolExecutor
ExecutorService ioPool = Executors.newFixedThreadPool(20);
ioPool.submit(() -> httpClient.send(request));
Q2:commonPool 什么时候用,什么时候不用?
答:
适合用 commonPool 的场景:
- 并行流(
parallelStream()) - 轻量级 CPU 密集型计算
- 临时性任务,不需要资源隔离
不适合用 commonPool 的场景:
- IO 密集型任务(线程数 = CPU 核数 - 1,不够用)
- 需要自定义线程数
- 需要资源隔离
- 生产环境长时间运行的任务
scss
// ❌ 危险:所有业务共用 commonPool
CompletableFuture.supplyAsync(() -> queryUser()); // 用 commonPool
CompletableFuture.supplyAsync(() -> queryOrder()); // 用 commonPool
// 可能互相影响
// ✅ 安全:自定义线程池隔离
ExecutorService userPool = Executors.newFixedThreadPool(8);
ExecutorService orderPool = Executors.newFixedThreadPool(8);
CompletableFuture.supplyAsync(() -> queryUser(), userPool);
CompletableFuture.supplyAsync(() -> queryOrder(), orderPool);
Q3:任务太小不要 fork,什么时候算"太小"?
答:fork 有开销(创建任务对象、入队、线程调度),如果任务执行时间 < fork 开销,就不值得 fork。
经验法则:
- 任务执行时间 < 1ms:不要 fork
- 任务执行时间 < 10ms:谨慎 fork
- 任务执行时间 > 100ms:值得 fork
scss
// ❌ 任务太小,fork 开销大于收益
if (n > 1) {
fork(new Task(n - 1)); // 任务只做一次减法,不值得 fork
return compute();
}
// ✅ 合适的阈值
private static final int THRESHOLD = 1000;
if (end - start <= THRESHOLD) {
// 直接计算,不 fork
return directCompute(array, start, end);
} else {
// fork 子任务
fork(new Task(array, start, mid));
return compute();
}
Q4:join() 会阻塞吗?与 invoke() 有什么区别?
答:
| 方法 | 行为 | 是否阻塞 | 是否触发工作窃取 |
|---|---|---|---|
fork() |
异步执行任务 | 不阻塞 | 不触发 |
join() |
等待任务完成 | 阻塞 | 不触发 |
invoke() |
fork + join | 阻塞 | 可能触发 |
scss
// 标准 fork/join 模式
Task left = new Task(data1);
Task right = new Task(data2);
left.fork(); // 异步执行
Result rightResult = right.compute(); // 同步执行
Result leftResult = left.join(); // 阻塞等待(但不触发窃取)
return merge(leftResult, rightResult);
// invoke 模式
Task task = new Task(data);
Result result = task.invoke(); // fork + join,可能触发窃取
最佳实践 :在 fork/join 模式中,最后一个任务用 compute() 同步执行 ,其他任务用 fork() 异步执行,最后用 join() 等待结果。这样可以避免不必要的工作窃取。
Q5:ForkJoin 的异常处理?
答:ForkJoinTask 的异常处理与普通任务不同:
php
// 方式 1:getException() 获取异常
ForkJoinTask<Integer> task = pool.invoke(new MyTask());
try {
Integer result = task.join();
} catch (Exception e) {
Throwable ex = task.getException(); // 获取原始异常
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
} else if (ex instanceof Error) {
throw (Error) ex;
} else {
throw new RuntimeException(ex);
}
}
// 方式 2:在任务内部处理
@Override
protected Integer compute() {
try {
return doCompute();
} catch (Exception e) {
// 记录日志
logger.error("Task failed", e);
// 返回默认值或重新抛出
return defaultValue;
}
}
// 方式 3:使用 CountedCompleter(更灵活)
// CountedCompleter 支持 onCompletion 回调,适合复杂场景
Q6:ForkJoinTask 可以取消吗?
答:可以,但需要配合中断机制:
csharp
ForkJoinTask<Integer> task = pool.invoke(new MyTask());
// 取消任务(mayInterruptIfRunning=true 会中断线程)
boolean cancelled = task.cancel(true);
if (cancelled) {
System.out.println("任务已取消");
}
// 检查任务状态
if (task.isCancelled()) {
System.out.println("任务被取消");
} else if (task.isCompletedNormally()) {
System.out.println("任务正常完成");
} else if (task.isCompletedAbnormally()) {
System.out.println("任务异常完成: " + task.getException());
}
🎯 最佳实践
Do / Don't 对照表
| ✅ Do | ❌ Don't |
|---|---|
| 递归可分的任务用 ForkJoinPool | IO 密集型任务用 ForkJoinPool |
| 设置合理的阈值(THRESHOLD) | 任务太小就 fork |
| 最后一个任务同步执行(compute) | 所有任务都 fork |
| 用 invokeAll() 同时 fork 两个任务 | 分两次 fork 两个任务 |
| 自定义线程池隔离业务 | 所有业务共用 commonPool |
| 处理任务中的异常 | 忽略异常导致任务丢失 |
| 用 CountedCompleter 处理复杂场景 | 用 RecursiveTask 处理复杂场景 |
性能优化技巧
scss
// 1. 合理设置阈值
private static final int THRESHOLD = computeOptimalThreshold();
// 2. 避免 fork/join 不平衡
// ❌ 不平衡:左任务大,右任务小
Task left = new Task(array, 0, array.length * 3 / 4); // 大任务
Task right = new Task(array, array.length * 3 / 4, array.length); // 小任务
// ✅ 平衡:左右任务差不多大
Task left = new Task(array, 0, array.length / 2);
Task right = new Task(array, array.length / 2, array.length);
// 3. 减少任务创建
// ❌ 每次创建新任务对象
for (int i = 0; i < n; i++) {
pool.invoke(new Task(i)); // 创建 n 个任务对象
}
// ✅ 复用任务对象
Task reusableTask = new Task();
for (int i = 0; i < n; i++) {
reusableTask.setData(i);
pool.invoke(reusableTask);
}
💡 面试要点
- ForkJoin 的核心思想:分治(Fork/Join)+ 工作窃取(Work Stealing)
- RecursiveTask vs RecursiveAction:有返回值 vs 无返回值
- 工作窃取的原理:每个线程有自己的双端队列,空闲线程从其他队列尾部窃取任务
- ForkJoinPool vs ThreadPoolExecutor:工作窃取 vs 共享队列
- fork/join 的最佳实践:最后一个任务同步执行,其他任务异步执行
- commonPool 的特点:全局共享、守护线程、线程数 = CPU 核数 - 1
- parallelStream 的底层:使用 ForkJoinPool.commonPool()
- 任务阈值:太小不要 fork,通常 > 100ms 才值得 fork
- 异常处理:getException() 获取原始异常
- 适用场景:递归可分的 CPU 密集型任务,不适合 IO 密集型
📝 总结
| 要点 | 记住这一句 |
|---|---|
| 核心价值 | 分治 + 工作窃取,让大任务自动并行 |
| RecursiveTask | 有返回值的分治任务 |
| RecursiveAction | 无返回值的分治任务 |
| 工作窃取 | 每个线程有自己的队列,空闲时从其他队列尾部偷任务 |
| fork/join 模式 | 最后一个任务 compute(),其他 fork() + join() |
| commonPool | 全局共享,parallelStream 底层,线程数 = CPU 核数 - 1 |
| 适用场景 | 递归可分的 CPU 密集型任务 |
| 避免坑 | 任务太小不要 fork,IO 密集型不要用 |
一句话心法 :ForkJoin 是"分而治之"思想的并行实现------能拆就拆,拆完再合,闲人帮忙。
📖 外部参考
- 📖 OpenJDK 17 ForkJoinPool 源码 --- 官方实现
- 📖 Java Concurrency in Practice --- Brian Goetz --- 并发编程圣经
- 📖 Fork/Join 框架的设计与实现 --- Doug Lea --- ForkJoin 作者原始论文
- 📖 Java Fork/Join 框架详解 --- Baeldung 教程
tags :#java #并发 #ForkJoin #工作窃取 #RecursiveTask #分治算法 #JDK17 #源码解析