synchronized 的重量级锁通过 ObjectMonitor 的 WaitSet 和 EntryList 协同实现线程同步:EntryList 管理等待获取锁的线程,WaitSet 管理调用 wait() 后释放锁并挂起的线程;notify() 将 WaitSet 中线程移入 EntryList 尾部,锁释放后唤醒 EntryList 首线程重新竞争。

Java 中 synchronized 的重量级锁依赖 ObjectMonitor 实现线程同步,其核心在于两个关键队列:WaitSet 和 EntryList。它们分工明确、协作紧密——EntryList 管理等待获取锁的线程,WaitSet 管理因调用 wait() 主动释放锁并挂起的线程。
EntryList:竞争锁的“排队通道”
EntryList 是一个 FIFO 队列(实际由 ObjectMonitor 内部的 _EntryList 字段维护),存放所有已阻塞、正等待获取该 monitor 锁的线程。当一个线程尝试进入 synchronized 块但发现锁已被占用时,它会被封装为 ObjectWaiter 节点,加入 EntryList 并调用 park() 挂起。
- 线程在
EntryList中不持有锁,也不执行临界区代码,纯粹处于“锁竞争预备态” - 当持有锁的线程退出同步块(或发生锁升级/降级等操作),JVM 会从
EntryList头部唤醒一个线程(通常为第一个),使其重新尝试获取锁 - 若此时锁仍不可用(如被其他刚唤醒线程抢先获得),该线程可能再次入队,形成“自旋+阻塞”结合的争抢逻辑
WaitSet:等待条件满足的“休眠池”
WaitSet 同样是 ObjectWaiter 组成的链表(对应 _WaitSet 字段),专门存放调用了 Object.wait() 的线程。这些线程已成功获取过锁,但在临界区内主动释放锁并挂起,等待某个业务条件成立(常配合 notify()/notifyAll() 使用)。
- 调用
wait()时,线程会原子性地释放 monitor 锁,并把自己加入WaitSet,然后调用park() -
notify()从WaitSet中取出一个线程,将其移入EntryList尾部(注意:不是直接唤醒执行,而是交由锁竞争机制调度) -
notifyAll()则把WaitSet全部节点迁移到EntryList尾部,后续由锁释放者统一唤醒竞争
两队列如何协同完成 wait-notify 流程
一个完整的 wait() → notify() → 唤醒执行流程,本质是 WaitSet 与 EntryList 的接力:
立即学习“Java免费学习笔记(深入)”;
- 线程 A 持有锁,在临界区内调用
wait():A 释放锁、入WaitSet、挂起 - 线程 B 进入同步块,修改共享状态后调用
notify():B 将WaitSet中一个线程(比如 A)摘出,插入EntryList尾部 - B 执行完退出同步块:释放锁,触发 JVM 唤醒
EntryList首个线程(即刚迁入的 A) - A 被唤醒后重新竞争锁,成功获取后继续执行
wait()后的代码
注意几个关键细节
这种协作机制隐含一些易忽略但重要的约束:
-
wait()、notify()必须在 synchronized 同步块内调用,否则抛IllegalMonitorStateException—— 因为只有持锁线程才能操作WaitSet -
notify()不保证唤醒的是“等待最久”的线程,JVM 不承诺 FIFO,仅保证从WaitSet中选一个迁移 - 被
notify()唤醒的线程需重新竞争锁,存在“惊群”和“伪唤醒”风险,所以wait()必须写在 while 循环中检测条件 -
EntryList和WaitSet均由 JVM 在 ObjectMonitor 内部管理,开发者无法直接访问或干预其结构


















