338. Java Stream API - 并行流的幕后英雄:Fork/Join 框架 & 工作机制详解

0 阅读3分钟

338. Java Stream API - 并行流的幕后英雄:Fork/Join 框架 & 工作机制详解


🎯 并行流的基本流程回顾

我们之前提到,并行流处理大致分为两步:

  1. 拆分数据源(Splitting the Source)
  2. 并行调度执行任务(Dispatching Work to Threads)

本节,我们重点讲第二步:拆完数据,怎么高效分发和执行?


🧠 幕后执行机制:Fork/Join 框架

并行流的执行,依赖 Java 的 Fork/Join 框架

什么是 Fork/Join?

Fork(分叉) ➡️ 把大任务拆成小任务 Join(合并) ➡️ 把多个子任务结果合并

它背后的线程池叫:

Common Fork/Join Pool JVM 启动时自动创建,线程数 ≈ CPU 核心数


🛠️ 执行过程(可视化比喻)

我们以一个处理 1,000 个元素的任务为例来看执行流程:

1️⃣ 第一个线程开始干活

list.parallelStream().map(...).collect(...)
  • 一个线程(假设是 T1)负责开始执行
  • 它先判断:这任务大不大?
  • 如果任务很大,就继续拆分

🧠 类比:像一个员工接了 1000 件工作后,决定继续外包给两个助理。


2️⃣ 任务被拆分为子任务

MainTask(1000 items)
   ├── SubTaskA (0~499)
   └── SubTaskB (500~999)
  • T1 将两个子任务丢进自己的等待队列中
  • 自己先处理其中一个,同时等待另一个结果

3️⃣ 等待任务完成、结果归并

SubTaskA → 再拆 → 小任务 → 各自执行 → 返回结果  
SubTaskB → 同理  
MainTask ← 收集两个结果 → 合并 → 返回最终值
  • 每个子任务可以继续拆!
  • 拆完的小任务若够小,就直接执行
  • 每个任务拿到两个子结果后合并返回

✅ 最终得到完整结果

这样一层一层拆、一层一层合并,最终整个任务被完整处理。


🦹‍♂️ Work Stealing:线程池的“自救机制”

刚开始,只有 T1 线程在干活,其他线程都在“闲着”。

那其他线程怎么办?

🎯 工作窃取机制(Work Stealing)

  • 每个线程有自己的任务等待队列
  • 如果一个线程空闲了,它会偷偷从其他线程的队列里**“偷”任务来干**

🧠 类比:像公司里的员工,如果自己没活干了,就去同事桌上“借点任务”干,保持全员忙碌。


💻 示例代码:演示 Fork/Join 并行流运行

List<Integer> list = IntStream.range(0, 1000).boxed().toList();

int sum = list.parallelStream()
              .mapToInt(i -> {
                  System.out.println(Thread.currentThread().getName() + " processing " + i);
                  return i;
              })
              .sum();

运行效果中会看到多个线程并发处理元素,且元素处理顺序混乱

ForkJoinPool.commonPool-worker-3 processing 1
ForkJoinPool.commonPool-worker-1 processing 2
ForkJoinPool.commonPool-worker-5 processing 0
...

🚨 并行流的“顺序混乱”问题

由于 Work Stealing 是非确定性的,任务可能会:

  • 被拆得不均
  • 被任意线程捡走执行
  • 导致元素处理顺序不可预测

❗这可能带来问题:

  • 你在 .map().forEach() 中写了副作用操作(比如写文件、修改共享变量)
  • 你依赖元素的顺序

建议:

  • 如果处理任务有顺序要求,请使用 .stream()(串行流)
  • 或者使用 .forEachOrdered() 强制顺序(但牺牲并行性能)

🎯 总结 & 重点小结

概念说明
Fork/Join 框架并行流底层的并行计算框架
Common PoolJVM 默认创建的线程池,线程数 = CPU 核数
拆任务(Fork)拆成更小任务直到足够小
合任务(Join)每个任务等子任务完成后合并结果
Work Stealing空闲线程自动从其他线程队列“偷任务”执行
顺序不稳定并行执行顺序不可预测,适合无副作用操作

✅ 小测试:以下哪些操作适合并行流?

  1. 给每个数字乘 2 后求和 ✅
  2. 将每行日志写入同一个文件 ❌
  3. 批量处理大图像缩放 ✅
  4. 为每个订单生成递增编号 ❌