synchronized锁升级是jvm自动触发的单向优化机制,按无锁→偏向锁→轻量级锁→重量级锁顺序演进,依据线程竞争强度动态调整,以平衡性能与安全。

synchronized 的锁升级(也叫锁膨胀)不是人为控制的流程,而是 JVM 在运行时根据线程竞争情况自动触发的优化机制。它本质是“用更轻量的方式应对低竞争,用更稳妥的方式应对高竞争”,目标是在保证线程安全的前提下,尽可能减少性能开销。
锁升级的四个阶段及其触发逻辑
整个过程从对象刚创建开始,状态随竞争强度逐步变化,且只升不降:
- 无锁状态:对象刚创建时,Mark Word 存储哈希码、分代年龄等信息,锁标志位为 01,偏向锁位为 0。此时没有任何线程持有锁。
- 偏向锁:当一个线程首次进入 synchronized 块时,JVM 通过 CAS 将该线程 ID 写入 Mark Word,并把偏向锁位设为 1、锁标志位保持 01。此后该线程再次进入无需同步操作,直接比对线程 ID 即可。
- 轻量级锁:当有第二个线程尝试获取同一把锁,发现当前是偏向锁且线程不匹配,JVM 会先撤销偏向锁(需进入安全点),然后让所有竞争线程在栈帧中创建 Lock Record,用 CAS 尝试将 Mark Word 替换为指向该记录的指针,锁标志位变为 00。失败则自旋重试(默认最多 10 次)。
- 重量级锁:自旋失败或竞争线程过多时,JVM 在堆中创建 Monitor 对象,把 Mark Word 更新为指向该 Monitor 的指针,锁标志位设为 10。此时未抢到锁的线程会被挂起,进入操作系统级别的阻塞队列。
锁升级的关键技术支撑点
整个机制高度依赖对象头中的 Mark Word 结构和底层原子操作:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- Mark Word 是 8 字节(64 位 JVM)的动态字段,不同锁状态下复用同一段内存存储不同含义的数据,比如线程 ID、栈帧指针、Monitor 地址等。
- 所有升级动作都围绕两个关键位判断:biased_lock(第 3 位) 和 lock(末两位)。JVM 通过读取这几位快速识别当前锁状态。
- 偏向锁撤销、轻量级锁自旋、重量级锁阻塞,都发生在 monitorenter 指令执行过程中,由 HotSpot 的 synchronizer.cpp 和 sharedRuntime.cpp 统一调度。
为什么锁升级不是越快越好
看似“早点升级到重量级锁”能更快结束争抢,但实际会带来明显代价:
- 重量级锁涉及用户态到内核态切换,每次阻塞/唤醒都有上下文切换开销,频繁触发会导致 CPU 缓存失效、调度延迟上升。
- 偏向锁撤销必须等待全局安全点(safepoint),高并发下大量线程同时触发撤销,会造成短暂停顿累积,表现为 P99 毛刺(如订单号生成器场景)。
- JDK 15+ 默认关闭偏向锁,正是因为它在多数现代服务端场景中反而成为负担——短临界区、多线程交替访问时,反复撤销再升级得不偿失。
实际开发中怎么看待锁升级
你不需要也不应该手动干预锁状态,但要理解其行为对代码的影响:
- 避免在高频调用路径上对同一个对象频繁加锁(尤其是实例方法锁 this),容易触发轻量级锁自旋或升级为重量级锁。
- 若明确知道多个线程会争抢某资源,不如一开始就用显式锁(如 ReentrantLock)配合公平策略,或改用无锁结构(如 AtomicInteger)。
- 排查并发毛刺时,开启
-XX:+PrintBiasedLockingStatistics和-XX:+TraceSafepoint可观察是否因锁升级导致 safepoint 停顿堆积。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










