锁消除和锁粗化是JVM运行时由JIT编译器(主要是C2)对热点代码做的动态优化:锁消除基于逃逸分析移除无竞争的锁,锁粗化合并连续高频的小粒度锁以减少同步开销。

锁消除和锁粗化不是编译期优化,而是 JVM 在运行时由 JIT 编译器(主要是 C2)对热点代码做的动态优化。它们不发生在 javac 编译字节码阶段,而是在程序运行中、方法被频繁调用后触发的即时编译环节。
锁消除:删掉根本不用的锁
它依赖逃逸分析判断一个对象是否只在当前线程内使用、不会被其他线程访问。如果确认无并发风险,JIT 就会把 synchronized 块整个移除——连 monitorenter/monitorexit 指令都不生成。
- 典型场景是方法内新建的 StringBuffer 或 StringBuilder(虽然 StringBuffer 方法带 synchronized,但单线程局部使用时锁被消除)
- 关键前提:对象是局部变量、未赋值给 static 字段、未传入未知方法、未放入共享容器(如 HashMap、ThreadLocal 以外的结构)
- 一旦出现 return sb、sb.toString() 后存入全局变量、或作为参数传给 logger.info() 等外部方法,逃逸分析失败,锁就保留
锁粗化:把零碎锁攒成一块
当 JIT 发现同一锁对象被连续、高频地加锁/解锁(比如循环体内反复 synchronized(lock)),且中间没有影响线程可见性的操作,就会把多个小同步块合并为一个大同步范围。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 例如 for 循环中 100 次调用 sb.append(),可能被优化为 synchronized(sb) { for (...) { sb.append(); } }
- 要求锁对象相同、代码逻辑连续、无异步副作用(如没写入 volatile 变量、没调用不确定行为的外部方法)
- 注意:粗化后临界区变长,在多线程争抢激烈时反而可能降低吞吐,JVM 会权衡语义正确性与性能收益
它们怎么协同提升性能
锁消除解决“锁多余”的问题,锁粗化解决“锁太碎”的问题。两者常叠加生效:
立即学习“Java免费学习笔记(深入)”;
- 单线程字符串拼接中:先消除 StringBuffer 内部锁(逃逸分析成功),再对循环中的多次 append 做粗化(合并 enter/exit)
- 结果是字节码指令减少几十条,避免了重复 monitor 操作、CAS、内存屏障及潜在上下文切换
- 但这些优化只在 C2 编译后的机器码中体现,解释执行或刚启动的冷代码里完全看不到
如何验证是否生效
不能靠看 Java 源码或字节码,得观察 JIT 编译后的实际行为:
- 加参数 -XX:+PrintCompilation 查看方法是否被 C2 编译
- 配合 hsdis 查看汇编,搜索 lock、cmpxchg、monitorenter 是否变少或消失
- 用 JMH 做基准测试,对比开启/关闭逃逸分析(-XX:-DoEscapeAnalysis)或强制解释执行(-Xint)的性能差异


















