会,JVM在逃逸分析证明锁对象不逃逸时消除synchronized;锁粗化则合并循环内重复加锁为单次大锁,二者均为JIT被动优化,不可依赖。

锁消除:JVM真会帮你删掉 synchronized 吗?
会,但只在明确能证明“锁对象逃逸不出方法”的情况下。比如 StringBuffer 在局部方法里被 new 出来又只在该方法内调用 append(),JVM(配合逃逸分析)可能直接去掉 StringBuffer 内部的锁。
- 锁消除不是代码层面的优化,你写
synchronized它还在,只是 JIT 编译后生成的机器码里没锁逻辑 - 必须开启逃逸分析(JDK 8+ 默认开,但若用了
-XX:-DoEscapeAnalysis就失效) - 常见误判场景:把局部对象传给
System.out.println()、放进Arrays.asList()、甚至赋值给static字段,都会导致逃逸,锁消除失败 - 不要为了“让 JVM 消锁”而强行拆方法或避免参数传递——可读性和维护性代价远高于那点同步开销
public String build() {
StringBuffer sb = new StringBuffer(); // ← 这个 sb 很可能被消除锁
sb.append("a").append("b"); // ← append 是 synchronized,但 JIT 可能直接内联+去锁
return sb.toString();
}
锁粗化:为什么连续加锁反而被合并成一个大锁?
JVM 发现同一把锁在一小段代码中被反复获取/释放(比如循环体内的 synchronized(obj)),就会把锁范围外扩到整个循环外——减少加锁/解锁次数,降低系统调用和上下文切换成本。
- 触发条件苛刻:必须是同一个锁对象、连续紧凑的临界区、且循环次数不固定为 0 或 1(否则无优化意义)
- 不适用于有长耗时操作的循环体(比如循环里调
Thread.sleep()或 I/O),粗化后会严重阻塞其他线程 -
synchronized块比synchronized方法更容易被粗化,因为后者锁范围已固定 - 粗化不是“更安全”,而是“更省开销”,如果循环本该细粒度控制并发,粗化反而扩大竞争面
典型易踩坑场景:
- 在 for 循环里反复synchronized(list) 做 add → 可能被粗化,但若 list 是共享容器,别人正 concurrently iterate,粗化后冲突概率更高
- 使用 Vector 的 add() + get() 组合,看似安全,实则每次调用都独立加锁,JVM 可能粗化,也可能不——取决于运行时 profile 数据
锁竞争没减下来?先确认是不是锁本身的问题
锁消除和粗化都是 JIT 的被动优化,无法手动触发,也不该作为并发设计的主要手段。真正压不住竞争,往往是因为:
立即学习“Java免费学习笔记(深入)”;
- 锁粒度太大:比如整个 service 方法用一把
private final Object lock = new Object()包着,而不是按数据维度分锁 - 锁对象太“热”:多个无关业务共用同一个
static final Object LOCK = new Object() - 用了
synchronized但没考虑替代方案:如ConcurrentHashMap、StampedLock、CAS 类变量(AtomicInteger)等本就不需要锁的场景 - 日志或监控埋点偷偷加了同步块(比如自定义
Logger的log()方法是 synchronized)
一个简单验证方式:用 jstack 看线程堆栈里 WAITING on java.lang.Object 的数量;再用 -XX:+PrintCompilation 观察是否真有锁消除/粗化的日志(搜索 “eliminate” 或 “coarsen”)。
别依赖这些优化写代码
它们生效高度依赖运行时环境:JDK 版本、JVM 参数、负载特征、甚至 GC 模式。同一段代码,在测试机上跑了 10 分钟没锁竞争,上线后 QPS 上升 5 倍,逃逸分析关闭,锁就全回来了。
- 写代码时,默认假设锁不会被消除、也不会被粗化
- 设计阶段优先考虑无锁结构或分段锁(如
ConcurrentHashMap的分段桶) - 如果真卡在锁上,先用
jmc或async-profiler定位热点锁对象,再决定是换数据结构、改锁范围,还是加读写分离
优化发生在 JIT 层,但问题出在你的线程模型里。









