锁消除仅在逃逸分析确认对象不逃逸时生效,锁粗化只合并同一锁的连续加解锁;二者均需c2编译器、足够调用次数及正确jvm参数支持。

锁消除在什么情况下真正生效
锁消除不是“写了 synchronized 就删”,而是 JVM 在逃逸分析确认对象不会被其他线程访问后,才敢把同步块干掉。它只对局部变量创建、仅在当前方法/线程内使用的对象有效。
常见错误现象:synchronized 看似没用,但加了就是不消除——大概率是对象逃逸了,比如被存入 static 集合、作为参数传给外部方法、或被内部类捕获。
- 必须开启逃逸分析(JDK 8+ 默认开,但若用
-XX:-DoEscapeAnalysis就彻底失效) - 对象不能被
System.out.println、日志框架、JSON 序列化等间接引用(这些操作常触发逃逸) -
StringBuffer的append方法里有synchronized,但局部新建的StringBuffer通常能被消除;换成StringBuilder则根本没锁,也谈不上消除
锁粗化会合并哪些相邻的 synchronized 块
锁粗化不是合并任意两个同步块,而是针对“对同一对象反复加锁解锁”的连续片段,且中间没有其他线程可能介入的临界点。它的目标是减少加锁/解锁次数,而非扩大临界区本身。
使用场景:循环体内的同步操作(如遍历集合时每次取元素都 synchronized(this)),或一连串短小的 synchronized 方法调用。
- 仅作用于同一把锁(即锁对象恒定,不能是循环中变化的
list.get(i)) - 中间不能有阻塞操作(如
wait()、Thread.sleep())、IO 或可能引发线程切换的调用 - 若同步块之间有非同步的耗时逻辑(如字符串拼接、复杂计算),粗化仍可能发生,但临界区变大,反而可能降低并发度
如何验证锁消除或锁粗化是否发生
不能靠肉眼或性能数字猜,得看 JIT 编译后的汇编或字节码优化痕迹。最轻量的方法是启用 JVM 内置诊断。
实操建议:
- 加参数
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(需 hsdis,较重) - 更实用的是
-XX:+UnlockDiagnosticVMOptions -XX:+PrintOptoAssembly(HotSpot Server VM 的优化树输出,显示锁节点是否被移除或合并) - 用 JMH 写对比测试:分别构造逃逸/不逃逸版本,观察
@Fork(jvmArgsAppend = "-XX:+PrintGCDetails")日志里是否出现锁相关优化提示(如 “eliminated”、“coarsened”) - 注意:这些优化只在 C2 编译器(server 模式)下生效,
-client或解释执行时完全不触发
为什么线上环境很少观察到明显效果
锁消除和锁粗化依赖即时编译的成熟度和运行时 profiling 数据。它们不是启动就生效,而是在方法被高频调用、触发 C2 编译(默认 10000 次调用阈值)后才介入。
容易踩的坑:
- 压测时间太短,方法还没被 C2 编译,看到的仍是解释执行行为
- 用了
-Xcomp强制编译,但跳过了 profiling 阶段,导致逃逸分析不准,优化失败 - 应用启用了
-XX:-UseBiasedLocking或-XX:+AlwaysPreTouch等参数,间接影响锁优化的触发路径 - 现代代码大量使用
java.util.concurrent工具类(如ConcurrentHashMap),它们本身无锁或用 CAS,根本不走 synchronized 优化路径
真正要关注的,不是“JVM有没有优化我的 synchronized”,而是“我是不是非得用 synchronized”。很多所谓可优化的同步块,其实本就可以用无锁结构替代——那才是更确定的性能提升点。










