并行流中使用同步集合会引发全局锁竞争,导致多线程退化为排队执行;应改用collect()无锁收集或分片聚合等方案规避瓶颈。

并行流(parallelStream)本身设计目标是**规避同步开销**,一旦在其中使用同步集合(如 Vector、Hashtable、Collections.synchronizedList() 或手动加锁的 ArrayList),就等于主动引入串行化瓶颈——这不是“变慢一点”,而是让多线程退化为事实上的排队执行。
为什么同步集合会成为全局性瓶颈
核心矛盾在于:同步集合的每个写操作(add、put、remove)都强制获取独占锁。当多个线程并发调用时:
- 只有一个线程能真正执行写入,其余全部阻塞等待,CPU 空转
- 锁竞争随线程数增加呈非线性恶化(尤其在高争用场景下,上下文切换频次飙升)
- 即使底层是
ConcurrentHashMap,若只用其put而不做分片聚合,仍无法避免结果合并阶段的集中写压力
典型错误模式与性能表现
以下代码看似“安全”,实则摧毁并行收益:
List<integer> result = Collections.synchronizedList(new ArrayList());
list.parallelStream()
.filter(x -> heavyComputation(x))
.forEach(result::add); // ❌ 高频锁竞争点
</integer>
实测表现(百万级数据、8核机器):
- 串行
stream().forEach():耗时约 1200ms - 上述并行 + 同步列表:耗时约 1800ms,CPU 利用率峰值仅 150%(远低于 800% 理论值)
- 改用无锁收集(
collect(Collectors.toList())):耗时降至 420ms,CPU 利用率稳定在 720%+
如何定位该瓶颈
不依赖猜测,用三类证据交叉验证:
-
线程堆栈:用
jstack <pid></pid>抓取运行中快照,高频出现- waiting to lock或at java.util.Collections$SynchronizedCollection.add -
监控指标:VisualVM 中观察
ForkJoinPool.commonPool的“Active Threads”长期卡在 1–2 个,而“Pool Size”满载,说明线程在等锁 -
对比实验:将同步集合换成
ThreadLocal<list></list>或直接用collect(),若耗时骤降 50%+,即可确认是同步集合拖累
真正可行的替代方案
目标不是“让同步集合变快”,而是绕过它:
-
优先用不可变收集:
collect(Collectors.toList())或collect(Collectors.toSet())—— 每个线程处理子段后生成独立集合,最后由框架合并,零共享写 -
需要中间状态聚合时,用并发容器 + 分片逻辑:例如用
ConcurrentHashMap存储键值对,但 key 设计为线程局部标识(如Thread.currentThread().getId()),再统一归并 -
极端场景需强顺序且高性能,放弃 parallelStream:改用
ExecutorService手动分区 +Future<list></list>收集,可控性更强,避免 commonPool 被污染










