锁消除的核心是jvm通过逃逸分析判定局部stringbuffer对象不会被其他线程访问,从而在jit编译(c2)生成机器码时跳过monitorenter/monitorexit指令;需满足对象未逃逸、启用逃逸分析与锁消除、代码达热点阈值等条件。

Java 锁消除在 StringBuffer 局部变量中生效,核心在于 JVM 运行时通过逃逸分析判定该对象“不会被其他线程访问”,从而跳过 synchronized 方法中的加锁/解锁指令。它不是删源码、也不是改字节码,而是在 JIT 编译生成本地机器码阶段做的优化。
为什么局部 StringBuffer 的锁能被消除
StringBuffer 的 append() 等方法都声明为 public synchronized,但同步本身只在存在共享竞争时才有意义。当一个 StringBuffer 实例:
- 仅在当前方法内 new 出来
- 没作为返回值传出
- 没赋给 static 字段、成员变量或全局容器(如 ConcurrentHashMap)
- 没传入可能跨线程执行的方法(如 executor.submit(runnable)、Thread.start())
- 没写入已逃逸对象的字段中(例如:把 sb 存进某个 static List 的元素里)
那么 JVM 就能确定它生命周期完全封闭在单一线程内——此时加锁纯属冗余,JIT 编译器(C2)会在编译热点方法时直接省略 monitorEnter/monitorExit 指令。
典型可触发锁消除的代码模式
以下写法大概率触发锁消除(前提是运行足够多次、进入 C2 编译):
StringBuffer sb = new StringBuffer(); sb.append("a").append("b");public String build(String a, String b) { StringBuffer sb = new StringBuffer(); sb.append(a).append(b); return sb.toString(); }- 在 for 循环体内反复创建并使用 StringBuffer,且不暴露引用
这些场景中,sb 是纯粹的线程私有对象,synchronized 只增加开销,无实际保护作用。
锁消除生效的必要条件
它不是自动“总开启”的功能,需同时满足:
- 运行在 Server 模式(HotSpot 默认)
- 启用逃逸分析:
-XX:+DoEscapeAnalysis(JDK 8+ 默认开启) - 显式启用锁消除:
-XX:+EliminateLocks(JDK 10+ 默认开启) - 代码被 JIT 认定为热点(通常执行超 10000 次),由 C2 编译器处理(可用
-XX:+PrintCompilation观察)
怎么确认锁真的被消除了
不能看 Java 源码或字节码(里面仍有 monitorenter),实用验证方式包括:
- 加参数
-XX:+PrintEscapeAnalysis -XX:+PrintCompilation,日志中出现 "lock elided" 或 "eliminated" - 用 JMH +
-prof perfasm查看汇编代码,确认没有 monitor 相关指令 - 对比开启/关闭逃逸分析的性能:关闭后 StringBuffer.append 性能通常下降约 15%,差的就是那几把被省掉的锁
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











