锁粗化是JIT编译器自动将同一锁的连续无干扰同步块合并为单次加锁的优化,如多个synchronized(sb){sb.append()}被合为一个大同步块,减少monitor开销,但要求无分支、无逃逸、无方法调用。

锁粗化不是开发者手动写的合并逻辑,而是 JVM 的 JIT 编译器(特别是 HotSpot 的 C2 编译器)在运行时自动识别并优化的一种行为。它针对的是「同一把锁、连续、无干扰」的多个同步块——比如对同一个 StringBuffer 对象反复调用带锁的 append() 方法。
为什么连续 append() 容易被粗化
StringBuffer.append() 是 synchronized 方法,每次调用都隐含一次 monitorenter + monitorexit。如果代码中出现类似下面的模式:
synchronized(sb) { sb.append("a"); }synchronized(sb) { sb.append("b"); }synchronized(sb) { sb.append("c"); }
JIT 在满足条件时会将这三段合并为一个大的同步块:synchronized(sb) { sb.append("a"); sb.append("b"); sb.append("c"); }
这样就把 3 次加锁/解锁降为 1 次,避免了 monitor 状态切换的开销。
粗化生效的关键前提
这个优化不会无条件发生,必须同时满足:
- 所有同步块使用的是**同一个锁对象**(如始终是
sb) - 中间**没有方法调用可能引发锁语义变化**(例如不调用
sb.toString()或任何可能触发逃逸的方法) - **没有分支结构**(如
if、for、while),否则控制流不可预测,JIT 不敢合并 - 锁对象本身**未逃逸出当前方法作用域**(即没被赋值给 static 字段、没传给未知方法、没放进全局容器)
实际效果与潜在代价
粗化后最直接的好处是减少锁操作次数,提升吞吐量。但要注意副作用:
- 线程持有锁的时间变长,原本可并发执行的其他逻辑可能被阻塞
- 如果粗化范围过大(比如误把本该分开的逻辑包进一个锁),反而降低并发度
- 循环体内的连续
append()不会被粗化——JVM 明确规避这种场景,防止锁持有时间失控
如何验证是否发生了粗化
启动 JVM 时加上参数:
-
-XX:+PrintCompilation:观察方法是否被 C2 编译 -
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining:查看内联日志中是否有coarsened字样
注意:冷启动或短生命周期测试几乎看不到效果,需保证方法被调用超 10000 次(默认阈值),让 C2 有足够机会介入优化。

















