parallelStream() 仅在数据量≥10万、单元素处理≥1ms、无状态操作、使用ArrayList/数组时才真正加速;forEach并发写共享集合会出错,应改用collect;parallel()与parallelStream()均走commonPool,但后者语义更清晰。

parallelStream() 能开启并行流,但不是开了就快——它只在特定条件下带来收益,盲目使用反而拖慢程序。
什么时候调用 parallelStream() 才真正有效
并行流的加速效果取决于任务“够重”、数据“够大”、结构“够友好”:
- 数据量 ≥ 10 万条(比如
ArrayList或数组),小集合如几千条基本不划算 - 单个元素处理耗时 ≥ 1ms(例如正则匹配、JSON 解析、简单数学运算不够,但带 IO 模拟或复杂计算可以)
- 操作必须无状态:不能修改外部变量,不能依赖执行顺序,
filter、map安全,forEach往共享ArrayList写就是典型错误 - 优先用
ArrayList、数组;避开LinkedList、Stream.generate()、Iterator等低效分割的数据源
为什么 forEach 在并行流里容易出错
常见错误是把串行写法直接搬过去,比如:
List<Integer> result = new ArrayList<>();
numbers.parallelStream()
.filter(n -> n % 2 == 0)
.forEach(result::add); // ❌ 非线程安全,可能丢数据、抛 <code>ConcurrentModificationException</code>根本原因是 forEach 不保证执行顺序,也不做同步,多个线程并发调用 add 会破坏 ArrayList 内部状态。
正确做法是交给流框架内部聚合:
List<Integer> result = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.collect(Collectors.toList()); // ✅ 线程安全,且自动合并Java 16+ 还可直接用 toList():
.collect(Collectors.toList()) → .toList()
parallel() 和 parallelStream() 的区别与选型
二者最终都走 ForkJoinPool.commonPool(),但语义和可维护性不同:
-
list.parallelStream():推荐,意图清晰,从源头声明并行,调试时一眼可见 -
list.stream().parallel():适合已有流对象需动态切换场景,但容易被链式调用掩盖,比如stream().filter(...).parallel().map(...)中parallel()的位置难察觉 - 一旦调用
parallel(),后续所有中间操作和终止操作都会并行执行,无法中途切回串行
如何验证并行是否真的起作用
不能只看耗时下降,得确认线程真参与了计算:
- 加日志观察线程名:
.peek(x -> System.out.println(Thread.currentThread().getName())),看到多个ForkJoinPool.commonPool-worker-线程说明生效 - 检查 CPU 使用率:处理期间多核是否被明显占用(如用
top -H或任务管理器) - 对比不同数据规模下的耗时曲线:1 万条 vs 100 万条,若前者并行更慢、后者快 30%+,说明符合预期
- 注意默认线程池大小 =
Runtime.getRuntime().availableProcessors(),超核数任务不会额外提速,还可能因上下文切换变慢
并行流最易被忽略的点是「副作用」和「数据源分割成本」——哪怕逻辑写对了,用 LinkedList 或在 filter 里偷偷改了某个静态计数器,结果就不可靠。


















