Java Stream API - 并行流中的副作用陷阱与顺序敏感操作
🎯 并行流看起来很美,但背后暗藏陷阱!
在使用 parallelStream() 时,我们希望的是:
- 🚀 利用多核 CPU 提升性能
- 🧩 自动并行处理每个元素
但现实中,如果你不小心写错了代码,结果可能是:
- ❌ 错误的输出
- 💥 异常崩溃
- 😵 性能反而变差
让我们来逐个拆解并行流的问题根源。
🧱 处理子流与处理完整流有何不同?
在并行流中,每一小块数据被拆成子任务(sub-stream),并由不同线程处理。但如果子任务之间共享状态或依赖顺序,就会出问题!
🧨 问题一:访问外部状态(副作用)
🤔 什么是'外部状态'?
外部状态 = 不属于流本身、但在流处理过程中被读写的变量或对象。 比如:
List<Integer> results = new ArrayList<>(); // ❗外部状态
IntStream.range(0, 1000).parallel().forEach(results::add); // 💥 多线程同时 add
🧪 实验:并发写入 ArrayList
List<Integer> ints = new ArrayList<>();
IntStream.range(0, 1_000_000).parallel().forEach(ints::add); // ❗ 非线程安全操作!
System.out.println("ints.size() = " + ints.size());
📌 实际输出(常见结果):
ints.size()=387122
甚至可能抛出:
Exception in thread "main"
java.lang.ArrayIndexOutOfBoundsException
🚨 原因分析:
ArrayList不是线程安全结构- 多个线程并发写入,导致数据覆盖、丢失或索引错乱
- 并行流默认使用 ForkJoinPool.commonPool,每个线程并发访问共享资源 ➡️ 出事了
✅ 正确做法:使用线程安全结构或无副作用方式
List<Integer> safe = IntStream.range(0, 1_000_000).parallel().boxed().collect(Collectors.toList());


