synchronized` 的“锁升级/路径解析

synchronized` 的“锁升级/路径

为什么会升级?什么时候升级?升级后发生了什么?该怎么写代码更高效?


0. 先给一个总图(JDK 17+ 的主线)

无锁 → 轻量级锁(用户态 CAS + 自旋) → 重量级锁(ObjectMonitor,阻塞/唤醒)

  • JDK 15 起“偏向锁”已移除,所以新版基本就是这三段路。

  • “升级”指的是:当竞争变激烈或等待变长,从更轻的路径切到更重、更稳的路径


1. 对象头与 Mark Word:锁状态的小“仪表盘”

每个 Java 对象都有对象头(Object Header),其中Mark Word用来装运行期信息(锁标志位、GC 年龄、哈希等)。
你不需要死记具体位宽,只要记住:Mark Word 的最低几位编码“锁态”,不同锁态时它的上层位含义不同。

典型(示意):

  • …01:无锁

  • …00:轻量级锁(Mark Word 指向“锁记录”的地址)

  • …10:重量级锁(Mark Word 指向 ObjectMonitor 的地址)

读法:看末位猜状态,看高位知道是指针还是哈希/信息。新版里偏向锁已经没了(老版本是 …101)。


2. 轻量级锁是怎么“上锁”的?

当线程进入 synchronized(obj)

  1. 在当前线程栈创建一条锁记录(Lock Record),把对象头里的 Mark Word CAS 到这条锁记录(理想情况下一次成功)。

  2. 成功 ⇒ 获得轻量级锁,进入临界区。

  3. 失败 ⇒ 说明已有线程持有,进入自旋(忙等几下,期望对方马上释放)。

关键词:CAS + 自旋(用户态),快速且无阻塞,适合短临界区、低竞争


3. 什么时候“升级”为重量级锁?

轻量级锁自旋不成或遇到必须阻塞的情形,就会“膨胀”为重量级锁(ObjectMonitor),把等待者放到队列里睡着,由持有者释放时唤醒一个继续抢锁。

常见触发条件(JDK 17+):

  • 自旋失败:转了几圈还抢不到(时间/次数超阈) → 升级

  • 持锁线程阻塞:临界区里 sleep()/IO/阻塞,别人等不来,只能升级

  • 真实多线程竞争:多个线程同时 CAS 失败,冲突明显 → 升级

  • wait()/notify() 语义:对象上调用 wait() 必须配合 ObjectMonitor → 直接/间接升级

  • 锁记录用尽:栈上可用锁槽不够(极少见) → 升级

记忆:短平快用自旋;等不来就上监视器。


4. 重量级锁里面发生什么:EntryList / WaitSet

膨胀后,JVM 为对象关联一个 ObjectMonitor,里面有几样关键部件:

ObjectMonitor
 ├─ owner        当前持锁线程
 ├─ recursions   可重入计数
 ├─ EntryList    “抢锁候车区”(没抢到锁的线程,在此阻塞 park)
 ├─ WaitSet      调用 wait() 的线程(等 notify/notifyAll 后转回 EntryList)
 └─ cxq          高并发下的临时竞争链,随后并入 EntryList

线程流转(不含 wait)

  • 线程抢不到锁 ⇒ 进 EntryListpark 阻塞

  • 持有者 exit 释放 ⇒ 从 EntryList 里唤醒一个(unpark),让它再去 CAS 抢

  • 抢到的成为新 owner,没抢到的继续回队列

wait/notify 的关系

  • 调用 wait() 的线程会释放锁并进入 WaitSet(不是 EntryList);

  • notify 选中后,从 WaitSet → EntryList等锁空了再抢(notify 不是“直接进临界区”)。

作用:省 CPU(阻塞睡眠代替空转),有序管理等待者,实现 Java 语义的公平性与正确性。


5. 代码+现象:如何“感受”一次升级

public class LockUpgradeDemo {
    static final Object LOCK = new Object();

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(() -> {
            synchronized (LOCK) {
                // 持锁较长时间,逼别人等不来
                try { Thread.sleep(200); } catch (InterruptedException ignored) {}
            }
        }, "T1");

        Thread t2 = new Thread(() -> {
            synchronized (LOCK) {
                System.out.println(Thread.currentThread().getName() + " got lock");
            }
        }, "T2");

        t1.start();
        Thread.sleep(10); // 让 T1 先拿住
        t2.start();
    }
}
  • T2 进来会先自旋几下(轻量级),发现 T1 不松手 ⇒ 升级为重量级,T2 进入 EntryList 阻塞。

  • T1 释放时唤醒 T2T2 抢到后输出 got lock

真看见对象头:用 JOL(Java Object Layout)在同步内/外打印对象头,观察 Mark Word 末位从 …00…10 的变化。
想看阻塞/唤醒:用 JFR(Flight Recorder)看“Java Monitor Blocked/Wait”事件。


6. 常见误区与澄清

  • “自旋就是浪费 CPU?”
    很短的临界区,自旋比阻塞/唤醒(内核态)更省;HotSpot 还有自适应自旋,历史上好抢就多转,反之少转/不转。

  • “升级后会降回去吗?”
    同一次持有期内不降级。锁完全不用了,JVM 可能在安全点把监视器清理(deflate),下次重新走轻量级路径。

  • “notify 立刻让对方进入临界区吗?”
    不。notify 只是把线程从 WaitSet → EntryList,它还得等锁空 & 重新竞争

  • synchronized 一定慢吗?”
    不是。新版 JVM 对无竞争/短竞争做了大量优化(消除、粗化、轻量级自旋)。慢通常是设计问题(大临界区、热点共享、锁顺序混乱)。


7. 开发者该怎么写,才能少走“重锁”?

  1. 缩短临界区:只包住必要的共享读写,不要把 IO、sleep、RPC 放进去。

  2. 减少共享:能线程本地就线程本地,能复制就复制(空间换时间)。

  3. 降低竞争:分片/分段(sharding),不同键/桶用不同锁。

  4. 选用并发容器ConcurrentHashMapLongAdderConcurrentLinkedQueue

  5. 用更合适的同步原语

  • 条件等待:ReentrantLock + Conditionwait/notify 更清晰

  • 读多写少:StampedLock / ReentrantReadWriteLock

  1. 避免“锁递归 + 多把锁交叉”:统一加锁顺序,减少死锁风险。

  2. 观测与验证:JFR 看 Monitor Blocked 时间分布、上下文切换;JOL 验证锁态;压测找热点。


8. 速记卡片(放在脑子里)

  • 升级主线:无锁 → 轻量级(CAS + 自旋)→ 重量级(ObjectMonitor)

  • 升级触发:自旋失败 / 持锁阻塞 / 真实竞争 / wait 语义 / 锁槽不足

  • 重量级结构owner + EntryList(抢锁队列) + WaitSet(wait 线程)

  • notify 语义:WaitSet → EntryList,再抢锁

  • 优化方向:缩小临界区、减少共享、用并发容器与合适的锁


9. FAQ(一分钟扫盲)

Q:为什么新版去掉偏向锁?
A:现代应用竞争模式变化、偏向撤销成本与收益不再划算,维护复杂度高,官方逐步移除简化实现。现在的轻/重锁配合 JIT 优化已足够高效。

Q:synchronizedReentrantLock 选谁?
A:简单同步选 synchronized(语义清晰,JIT 优化多)。需要可中断等待、超时、多个条件队列、公平锁等高级能力,选 ReentrantLock

Q:如何知道是否频繁进入重量级?
A:JFR 里看 Java Monitor Blocked、线程阻塞时间、上下文切换次数、热点对象。如果很多线程在同一把锁上阻塞,就要优化设计。


10. 一句话总结

synchronized 会先以“轻量级自旋”快速尝试;等不来就升级到“重量级阻塞”通过 ObjectMonitor 管理等待者(EntryList)与 wait 线程(WaitSet)。
写代码时要做的,就是:让临界区短小、减少共享竞争,让系统大多数时候都停留在无锁/轻竞争的快路径上。


posted on 2025-11-10 02:46  滚动的蛋  阅读(0)  评论(0)    收藏  举报

导航