锁竞争会使并行流退化为串行甚至更差,关键看争抢频率与持有时间;应通过JFR或async-profiler定位阻塞点,量化加速比与CPU利用率,优先用无锁聚合、reduce归约或ThreadLocal缓冲绕过锁。

直接看锁竞争本身——它不是“慢一点”,而是让并行流从多线程退化成事实上的串行执行,甚至更糟。显式写锁(比如 synchronized 块、ReentrantLock.lock()、或对共享可变变量的 volatile + CAS 操作)一旦成为高频路径,就会成为整个并行流的瓶颈点。
识别锁竞争是否真正发生
不能只凭“用了锁”就断定有竞争,关键看争抢频率和持有时间:
- 用 JFR(Java Flight Recorder)或 async-profiler 抓取运行时热点,观察
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await或Unsafe.park是否频繁出现,线程是否大量阻塞在锁入口 - 检查锁保护的代码段:如果里面包含 I/O、远程调用、复杂计算或内存分配,哪怕只有一处,也会显著拉长锁持有时间,放大排队效应
- 对比并行流拆分粒度与锁临界区大小——若每个
forEach子任务平均只做几毫秒工作,却要花 1–2 毫秒去抢锁,那并行几乎无收益
量化锁带来的吞吐衰减
性能损耗不单是“变慢”,而是随并发线程数增加呈现非线性下降:
- 测基线:用单线程顺序执行任务,记录总耗时
T₁ - 测并行:分别用
ForkJoinPool.commonPool()默认并行度(如 8)、以及手动指定parallelStream().forEach(...)在不同线程数下运行,记录实际耗时Tₙ - 算加速比:
Sₙ = T₁ / Tₙ;理想值应接近n,若S₈ ≈ 1.2,说明 8 个线程实质只发挥了约 1.5 个线程的算力 - 进一步看 CPU 利用率:若 top 显示用户态 CPU 不足 30%,但锁等待线程数持续高位,基本确认是锁争用压制了并行度
定位共享状态的设计缺陷
并行流本意是“无状态处理”,显式写锁往往暴露了不恰当的数据组织方式:
- 检查是否把本该局部累积的结果(如计数、列表拼接、Map 合并)强行放在类字段或静态变量里,再用锁保护——这违背了并行流的 reduce/fold 模式
- 确认是否误将线程不安全对象(如
SimpleDateFormat、ArrayList)作为闭包捕获,导致多个子任务共用同一实例 - 留意“伪共享”(False Sharing):即使变量逻辑独立,若它们落在同一 CPU 缓存行(64 字节),频繁写入仍会触发缓存一致性协议开销,表现类似锁竞争
替代方案优先级排序
不是“怎么优化锁”,而是“如何绕过锁”:
- 改用无锁聚合:用
parallelStream().map(...).collect(Collectors.groupingByConcurrent())或Collectors.toConcurrentMap(),底层基于ConcurrentHashMap分段写入 - 用归约(reduce)代替副作用:把
forEach中的写操作转为map → reduce链,让每个线程先本地计算,最后合并,彻底消除临界区 - 引入线程本地缓冲:对高频小写入(如日志计数),用
ThreadLocal先缓存,批量 flush 到共享目标,大幅降低锁调用频次 - 必要时降级为显式 ForkJoinTask:当业务逻辑复杂、无法用标准流接口表达时,手动控制任务划分与合并,避免流框架隐式同步开销










