锁粗化是jvm jit编译器(主要是c2)自动将连续、同锁对象的多次synchronized操作合并为一次大范围加锁的优化技术,需满足锁对象相同、字节码紧邻、未逃逸且方法为热点代码等条件。

锁粗化不是手动写的,是 JVM 的 JIT 编译器(主要是 HotSpot 的 C2 编译器)在运行时自动完成的优化。它把原本分散、连续、针对同一锁对象的多次 synchronized 操作,合并成一次更大范围的加锁和解锁,从而减少底层开销。
触发合并的关键前提
JVM 不会随便合并,必须同时满足这几个条件:
- 锁对象完全相同,且在整个过程中没有被重新赋值(比如
synchronized(lock)中的lock始终是同一个引用) - 这些同步操作在字节码层面“紧邻”,中间没有插入可能影响线程可见性的非同步代码(如写入 static 变量、调用外部方法、抛异常、分支跳转等)
- 锁对象经过逃逸分析判定为未逃逸(例如局部 new 出来的
StringBuffer或Object),不会被其他线程访问到 - 对应方法已被 JIT 判定为热点代码(默认执行超 10000 次),触发 C2 编译并启用高级优化
典型合并方式:从循环内到循环外
最常见的是把循环体内的每次加锁,提升为整个循环只加锁一次。例如:
原始代码:
for (int i = 0; i
JVM 识别出:100 次对 lock 的 monitorenter/monitorexit,中间无干扰,且 lock 未逃逸 → 合并为:
synchronized(lock) {
for (int i = 0; i
效果是:指令数从 200 次(100×enter + 100×exit)降到 2 次,避免了重复的 Monitor 状态检查、Mark Word 更新、CAS 尝试和潜在的线程状态切换。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
不只是 for 循环:相邻同步块也能合并
即使没写循环,只要多个 synchronized 块连续出现、锁对象一致,也可能被粗化。例如:
synchronized(sb) { sb.append("a"); }
synchronized(sb) { sb.append("b"); }
synchronized(sb) { sb.append("c"); }
JVM 可能重写为:
synchronized(sb) {
sb.append("a");
sb.append("b");
sb.append("c");
}
这种合并常见于 StringBuffer.append() 连续调用——每个 append() 都是 synchronized 方法,JIT 内联后发现本质就是一连串对同一对象的同步调用,于是直接包裹进一个大同步区。
为什么不能盲目扩大?它有安全底线
粗化绝不会破坏原有语义。以下情况会阻止合并:
- 同步块之间穿插了可能被其他线程读取的状态更新(比如
counter++后立刻被别的线程观察) - 调用了
wait()、notify()或任何可能改变锁语义的方法 - 锁对象在不同块中实际不同(比如一会儿
this,一会儿new Object()) - 循环体内有
break、return或try-catch,导致控制流不连续
换句话说:JVM 只在确认“合并前后对外可见行为完全一致”的前提下才动手,不是为了快而牺牲正确性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










