锁粗化和锁消除是jvm jit编译器在满足逃逸分析、高频同步等条件后触发的动态优化,需代码稳定执行万次以上才能生效,二者分别消除无竞争锁和合并连续细粒度锁以提升性能。

锁粗化和锁消除不是靠“写对 synchronized”就自动生效的,它们是 JVM 在运行时通过 JIT 编译器做的动态优化,必须满足特定条件才能触发。真正起作用的前提是:代码已稳定执行足够多次(默认 10000 次),触发 C2 编译;同时对象生命周期、引用方式、线程访问模式都符合逃逸分析或高频同步模式的判定逻辑。
锁消除:去掉根本不需要的锁
核心依据是逃逸分析 —— JVM 判断一个对象是否可能被其他线程访问。如果确定不会逃逸,那它上面的 synchronized 就是冗余的,直接删掉。
- 局部新建、未返回、未传参、未存入 static 字段或全局容器的对象,比如方法内 new 的 StringBuffer,它的 append() 虽带 synchronized,但锁会被消除
- 一旦出现 return sb、sb.toString() 后赋值给 static 变量、或作为参数传给其他方法,逃逸分析就会失败,锁保留
- 验证是否生效:用 -XX:+UnlockDiagnosticVMOptions -XX:+PrintEscapeAnalysis 看日志;更可靠的是用 JMH + perfasm 观察热点指令里是否还有 monitorenter/monitorexit
锁粗化:合并高频重复的细粒度锁
不是“把锁范围拉大”就有益,而是针对连续、同对象、高频率的加锁/解锁序列,把多个小同步块合并成一个大同步块,减少锁操作本身开销。
- 典型场景:循环体内反复调用同一个对象的同步方法,比如 for 循环中连续调用 sb.append(),且循环次数足够多(通常 ≥10)
- 关键限制:sb 必须是循环外创建的同一实例;若每次循环 new 一个新 StringBuffer,粗化失效
- 观察效果:用 -XX:+PrintCompilation 查看编译日志,若 StringBuffer::append 的编译版本号上升、字节数下降,可能是粗化后内联或简化了同步逻辑
两者如何配合提升性能
它们解决的是不同层面的开销:锁消除干掉无竞争的锁指令;锁粗化减少频繁锁操作的系统调用与上下文切换成本。在合适场景下叠加使用,效果更明显。
- 例如单线程字符串拼接:先消除 StringBuffer 的锁(逃逸分析成功),再对循环内的 append 序列做粗化(合并为一次 enter/exit),比原始代码少几十条字节码指令
- 但要注意副作用:粗化后临界区变长,在多线程争抢激烈时可能加剧排队,反而比原来更慢
- 实际调优时,别只比 System.nanoTime(),要盯住 JIT 编译后的机器码 —— 用 -XX:+PrintAssembly 搜索 lock 前缀或 cmpxchg 指令,确认同步原语是否真被移除或合并
容易被忽略的关键前提
这些优化不是“开了就有效”,依赖 JVM 运行时状态和代码结构细节:
- 冷启动、短生命周期应用(如 CLI 工具)、压测时间太短,JIT 来不及编译,优化不生效
- 默认开启的偏向锁在初期争抢时有额外开销,高并发下可尝试关掉(-XX:-UseBiasedLocking)对比 baseline
- StringBuffer 本身已是过时实践,即使锁被消除,其内部扩容仍比 StringBuilder 多一次数组拷贝,不应作为性能标杆
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











