锁粗化减少的是monitorenter/monitorexit指令执行开销,包括cas更新mark word、内存屏障、线程状态切换及用户态/内核态切换等。

锁粗化是 JVM 在 JIT 编译阶段自动完成的优化,它不靠程序员手动改写,而是把原本分散、高频、针对同一对象的多次加锁解锁操作,合并成一次更大范围的同步块,从而直接减少底层锁机制的执行次数和相关开销。
锁粗化减少的是哪些具体开销
每次 synchronized 进入和退出,JVM 都要执行 monitorenter 和 monitorexit 字节码指令。这些指令背后涉及:
- CAS 操作更新对象头中的 Mark Word
- 内存屏障(Memory Barrier)保证可见性
- 可能触发线程状态切换(如挂起/唤醒)
- 与操作系统互斥原语(如 mutex)交互带来的用户态/内核态切换
锁粗化后,这些动作从“循环 100 次就执行 200 次”,变成“只执行 2 次”,显著释放 CPU 周期。
编译器在什么条件下会触发锁粗化
JIT 编译器(主要是 C2)不是盲目合并,必须同时满足几个关键条件:
- 所有同步块锁定的是同一个对象(比如都是
synchronized(lock)) - 这些同步块在代码中“紧邻”或逻辑连续,中间没有其他线程可见的共享状态修改
- 锁对象未发生逃逸,或即使逃逸也确认无并发干扰(例如局部变量
new Object()被反复用于多个synchronized块) - 同步块之间没有调用未知外部方法(如
logger.info())、没有写入 static 字段或全局容器
典型可被粗化的代码模式
以下写法容易被 JIT 识别并优化:
-
循环体内重复加锁:原始写法
for (int i = 0; i 可能被粗化为 <code>synchronized(lock) { for (...) { work(i); } } -
相邻小同步块:连续三行
synchronized(lock) { a.set(x); }、synchronized(lock) { b.get(); }、synchronized(lock) { c.clear(); }可能合并为一个大块 -
链式调用 synchronized 方法:对同一对象连续调用多个
synchronized实例方法(如sb.append("a").append("b").append("c")),且sb是局部未逃逸对象
它不是手动控制的,但你可以间接验证
开发者无法开启或关闭锁粗化,但可通过工具观察效果:
- 用
-XX:+PrintCompilation查看方法是否被 C2 编译 - 配合
hsdis查看汇编,对比粗化前后monitorenter/monitorexit指令数量 - 做基准测试:分别用
-Xint(纯解释执行)和默认模式运行含高频小同步块的代码,性能差异往往就来自锁粗化等 JIT 优化
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











