Skip to content
进阶

一句话答案

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();

文档完

复习建议:

  1. 线程池六参数(Q6)+ 执行流程(Q7)是手写代码题常考点,建议手写一遍 ThreadPoolExecutor 的构造
  2. synchronized 锁升级(Q12)是字节/美团必问的连环追问链,无锁→偏向→轻量→重量整条链路要背熟
  3. AQS 原理(Q18)是 ReentrantLock/Semaphore/CountDownLatch 的通用答案,一题通三题
  4. ThreadLocal 内存泄漏(Q25)是加分题,能说出 Entry 弱引用 key + 强引用 value 的组合以及 remove() 的重要性,基本稳了
  5. 虚拟线程(Q29)是 JDK 21 新特性高频题,重点掌握与平台线程的区别、pinning 问题、以及 Spring Boot 集成方式
  6. 并发工具三件套(Q32)要能说清各自的等待模型和场景;CompletableFuture(Q33)是项目中最实用的异步编排工具,allOf + join 的并行查询模式要能手写
  7. ForkJoinPool 工作窃取(Q34)是 parallelStream 的底层答案,面试常追问"为什么 parallelStream 不适合 IO 操作"
追问与易错

追问方向:

  • 工作窃取从队列哪头偷?为什么?
  • parallelStream 的坑?
  • 什么场景不适合 Fork/Join?

易错点:

  • ❌ parallelStream 一定比串行快——小数据量反而慢
  • ❌ 在 parallelStream 中做 IO——会阻塞 common pool

💡 记忆锚点

Fork/Join 是分治流水线:大活拆成小活分给工人(fork),干完再拼回去(join)。工作窃取像吃自助餐,自己盘子空了就从别人盘子远端夹一块——各取各端减少争抢。parallelStream 底层就是它,但共用一个餐桌(commonPool),一个人吃太慢全桌跟着等。