锁消除是JVM在运行时基于逃逸分析确认对象未逃逸后,由JIT编译器省略monitorenter/monitorexit指令的优化;它不修改字节码,依赖对象仅在当前线程内使用且不满足任何逃逸条件。

锁消除不是你删掉 synchronized,而是 JVM 在运行时发现“这把锁根本没必要”,悄悄跳过加锁逻辑。它依赖逃逸分析判断对象是否只在当前线程内使用,一旦确认没逃逸,JIT 编译器就在生成机器码时直接省掉 monitorenter 和 monitorexit 指令。
逃逸分析是锁消除的前提
JVM 先分析一个对象的“活动范围”:如果它只在当前方法里创建、使用,且不满足以下任一条件,就判定为“未逃逸”:
- 没有作为返回值返回给调用方
- 没有赋值给静态变量或全局容器(如
static List> CACHE) - 没有传入可能跨线程调用的方法(比如
executor.submit(runnable)) - 没有被写入堆上其他已逃逸对象的字段中
只有逃逸分析确认对象私有,JIT 才敢把它身上的同步操作整个抹掉——因为不存在多线程竞争,加锁纯属浪费。
典型可被消除的代码模式
这些场景下,即使用了线程安全类,锁也常被 JIT 消除:
-
StringBuffer sb = new StringBuffer(); sb.append("a").append("b");——append()是 synchronized,但sb没逃逸,锁被干掉 -
Vector<string> list = new Vector(); list.add("x"); list.size();</string>—— 局部使用,不共享,同步逻辑被跳过 - 自定义的
synchronized void increment() { count++; },若对象在方法内new出来并立即调用,同样可能被优化
注意:是否生效跟用的是 synchronized 还是 ReentrantLock 无关,关键看对象生命周期是否封闭。
如何验证锁消除是否发生
它发生在 JIT 编译阶段,不是字节码层面的修改,所以不能靠反编译看。实用验证方式有:
- 启动参数加
-XX:+PrintEscapeAnalysis -XX:+PrintCompilation,观察日志中是否有eliminated或lock elided - 用 JMH +
-prof perfasm查看热点方法汇编,确认monitorenter指令是否消失 - 对比开启/关闭逃逸分析的性能差异:加
-XX:-DoEscapeAnalysis后,StringBuffer.append的耗时通常上升约 15%
需注意:锁消除需代码执行足够多次(默认 10000 次),触发 C2 编译后才生效;短生命周期应用或压测时间太短,看不到效果。
锁消除不是万能的,也有失效边界
常见误判导致逃逸分析失败,锁无法消除:
- 写了
return sb;或return sb.toString();,哪怕只返回一次,JVM 就不敢保证调用方不把它传给别的线程 -
sb.toString()的结果被存进static Map或缓存池,间接导致sb逃逸 - 循环中反复
sb = new StringBuffer(),JIT 可能放弃分析或粗化失败 - 在调试模式(
-Xdebug)或某些低配 JVM 参数下,逃逸分析可能被禁用
它不改变语义,也不要求你改代码,但理解它的触发条件,能帮你写出更易被优化的局部逻辑。










