外观
一句话答案
Fork/Join 分治并行框架,工作窃取算法让空闲线程从其他线程队列尾部偷取任务,parallelStream 底层基于它。
核心要点
ForkJoinPool 核心思想:分治 + 工作窃取
Fork(分叉):将大任务递归拆分成小任务
Join(合并):将小任务的结果合并成大任务的结果工作窃取(Work-Stealing)算法:
每个工作线程有自己的双端队列(Deque):
Thread 1: [A1, A2, A3] ← 从头部取自己的任务
Thread 2: [B1, B2] ← 从头部取自己的任务
Thread 3: [空] ← 自己队列空了
Thread 3 的队列空了,不会闲着:
→ 从其他线程(如 Thread 1)的队列尾部"偷"一个任务
→ Thread 3 偷走 A3 来执行
关键设计:
自己的任务从头部取(LIFO,大任务先执行,再细分)
偷别人的任务从尾部取(FIFO,偷最大的任务来执行)
→ 减少竞争(各取各的端,冲突概率低)ForkJoinTask 使用示例:
java
class SumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start, end;
private static final int THRESHOLD = 10000;
@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) / 2;
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(4);
Long result = pool.invoke(new SumTask(array, 0, array.length));parallelStream 与 ForkJoinPool 的关系:
parallelStream() 底层使用 ForkJoinPool.commonPool()
commonPool 的线程数 = Runtime.getRuntime().availableProcessors() - 1
问题:整个 JVM 共享一个 commonPool
→ 一个 parallelStream 执行慢(如含 IO 操作)→ 阻塞公共线程
→ 影响其他 parallelStream / CompletableFuture 的执行
解决:自定义 ForkJoinPool 提交任务
ForkJoinPool customPool = new ForkJoinPool(16);
customPool.submit(() ->
list.parallelStream().map(...).collect(...)
).get();文档完
复习建议:
- 线程池六参数(Q6)+ 执行流程(Q7)是手写代码题常考点,建议手写一遍 ThreadPoolExecutor 的构造
- synchronized 锁升级(Q12)是字节/美团必问的连环追问链,无锁→偏向→轻量→重量整条链路要背熟
- AQS 原理(Q18)是 ReentrantLock/Semaphore/CountDownLatch 的通用答案,一题通三题
- ThreadLocal 内存泄漏(Q25)是加分题,能说出 Entry 弱引用 key + 强引用 value 的组合以及 remove() 的重要性,基本稳了
- 虚拟线程(Q29)是 JDK 21 新特性高频题,重点掌握与平台线程的区别、pinning 问题、以及 Spring Boot 集成方式
- 并发工具三件套(Q32)要能说清各自的等待模型和场景;CompletableFuture(Q33)是项目中最实用的异步编排工具,allOf + join 的并行查询模式要能手写
- ForkJoinPool 工作窃取(Q34)是 parallelStream 的底层答案,面试常追问"为什么 parallelStream 不适合 IO 操作"
追问与易错
追问方向:
- 工作窃取从队列哪头偷?为什么?
- parallelStream 的坑?
- 什么场景不适合 Fork/Join?
易错点:
- ❌ parallelStream 一定比串行快——小数据量反而慢
- ❌ 在 parallelStream 中做 IO——会阻塞 common pool
💡 记忆锚点
Fork/Join 是分治流水线:大活拆成小活分给工人(fork),干完再拼回去(join)。工作窃取像吃自助餐,自己盘子空了就从别人盘子远端夹一块——各取各端减少争抢。parallelStream 底层就是它,但共用一个餐桌(commonPool),一个人吃太慢全桌跟着等。