锁消除和锁粗化是JVM JIT编译器(主要是C2)自动触发的运行时优化:锁消除基于逃逸分析移除无竞争的锁(如局部StringBuffer的synchronized),锁粗化则合并同一对象连续高频的细粒度锁(如循环内多次append),二者叠加可显著减少monitorenter/monitorexit指令及上下文切换开销,但需代码稳定执行万次以上、满足逃逸与同步模式条件才生效。

锁消除和锁粗化是 JVM 在运行时由 JIT 编译器(主要是 C2 编译器)自动完成的两种底层优化,不依赖程序员手动调整 synchronized,而是在代码稳定执行、满足特定条件后动态生效。
锁消除:靠逃逸分析删掉“没人争”的锁
JIT 通过逃逸分析判断一个对象是否只被当前线程使用、从未发布到其他线程或全局作用域。如果确认无并发风险,就直接跳过加锁逻辑,连 monitorenter/monitorexit 字节码都可能不生成。
- 典型适用对象:方法内
new的局部对象,比如StringBuffer、StringBuilder、Object实例 - 必须满足:不赋值给
static字段、不传入未知方法(如logger.info())、不放入共享容器(如HashMap、ConcurrentLinkedQueue) - 一旦出现
return sb或sb.toString()后存入静态变量,逃逸分析失败,锁保留
锁粗化:把高频小锁合并成一次大范围同步
当 JIT 发现对同一锁对象的多个 synchronized 块在代码中连续出现、中间没有分支、调用或逃逸行为,就会将它们合并为一个更大的同步区域,减少锁操作本身的开销。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 常见场景:循环体内反复调用同一个对象的同步方法,如
for中多次sb.append() - 关键前提:锁对象是同一个实例(不能每次循环
new一个新对象) - 不会粗化的情况:同步块之间有
if分支、方法调用(尤其是可能触发锁语义的)、或锁对象实际不同(如混用this和new Object())
两者如何配合起效
它们解决的是不同层面的开销:锁消除去掉根本不需要的锁指令;锁粗化减少高频锁操作带来的上下文切换和系统调用成本。在合适场景下叠加使用,效果更明显。
立即学习“Java免费学习笔记(深入)”;
- 例如单线程字符串拼接:
StringBuffer先因逃逸分析成功被锁消除,再对其循环调用序列做粗化,最终只保留一次 enter/exit - 但要注意副作用:粗化后临界区变长,在多线程争抢激烈时可能加剧排队,反而降低吞吐量
- 这些优化只在 C2 编译后生效,需方法执行达默认阈值(通常 ≥10000 次),冷启动或短生命周期测试中基本看不到效果
怎么验证是否发生优化
不能靠肉眼读 Java 代码判断,得借助 JVM 工具观察编译行为和生成代码:
- 加参数
-XX:+PrintCompilation查看方法是否被 C2 编译 - 用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintEscapeAnalysis看逃逸分析日志 - 配合
hsdis查看汇编,搜索是否还有lock前缀或cmpxchg指令 - JMH +
perfasm对比热点指令中monitorenter出现频次是否下降

















