下面系统性地总结 Java 线程状态变化 与 _owner、_cxq、_EntryList、_WaitSet 四个重量级锁核心结构之间的交互。
一、线程状态与四个结构的关系概览
| 线程状态 | 所属结构 | 说明 |
|---|---|---|
RUNNABLE (持有锁) |
_owner |
当前持有锁的线程,正在执行同步块 |
BLOCKED |
_cxq 或 _EntryList |
等待获取锁的线程,处于阻塞状态 |
WAITING / TIMED_WAITING |
_WaitSet |
已持有锁但主动调用 wait(),等待被唤醒 |
NEW / TERMINATED |
无 | 尚未参与锁竞争,或已结束 |
二、状态转换与结构交互的详细流程
1️⃣ NEW → RUNNABLE(未涉及锁)
- 不涉及
_owner/_cxq/_EntryList/_WaitSet。
2️⃣ 线程尝试进入 synchronized 并成功获得锁
- 前提 :
_owner == NULL。 - 操作 :通过 CAS 将
_owner设置为当前线程。 - 状态 :线程变为
RUNNABLE(持有锁)。 - 结构变化 :
_owner = currentThread。
3️⃣ 线程尝试进入 synchronized 但锁已被占用 → RUNNABLE → BLOCKED
- 前提 :
_owner != NULL且不是当前线程。 - 操作 :
- 当前线程被封装为
ObjectWaiter节点。 - 通过 CAS 头插 将节点加入
_cxq(LIFO 栈)。 - 可能先自旋,若仍失败则调用
park()阻塞。
- 当前线程被封装为
- 状态 :从
RUNNABLE变为BLOCKED。 - 结构变化 :线程加入
_cxq头部,_owner不变。
4️⃣ 锁释放(_owner 退出 synchronized)→ 唤醒 _EntryList 或 _cxq 中的线程
- 前提 :当前线程是
_owner,即将释放锁。 - 操作 (以默认
QMode=0为例):- 如果
_EntryList非空 → 取出头部线程唤醒。 - 如果
_EntryList为空 → 将_cxq中所有线程批量转移到_EntryList(通常反转顺序,使 LIFO 变 FIFO),然后唤醒_EntryList头部线程。
- 如果
- 被唤醒的线程 :从
BLOCKED变为RUNNABLE(尝试 CAS 成为新_owner)。 - 结构变化 :
- 原
_owner = NULL。 _cxq被清空,_EntryList获得一批等待线程。
- 原
5️⃣ 持有锁的线程调用 Object.wait() → RUNNABLE → WAITING
- 前提 :当前线程是
_owner。 - 操作 :
- 将当前线程加入
_WaitSet尾部。 - 释放锁:设置
_owner = NULL。 - 调用
park()阻塞当前线程。
- 将当前线程加入
- 状态 :从
RUNNABLE变为WAITING。 - 结构变化 :
_owner = NULL。_WaitSet增加当前线程。- 锁释放后,会触发 第 4 步 的唤醒流程。
6️⃣ 其他线程调用 notify() / notifyAll() → 唤醒 _WaitSet 中的线程
- 前提 :调用
notify()的线程必须持有同一个对象的锁(即_owner == 该线程)。 - 操作 (以
notify()为例):- 从
_WaitSet中取出一个线程(通常是头部)。 - 将该线程移入
_EntryList(默认策略)或_cxq(取决于 JVM 参数)。
- 从
- 被移出的线程 :状态仍为
WAITING,直到被唤醒并重新竞争锁。当它被移入_EntryList后,其状态实际上变为BLOCKED(等待锁)。 - 结构变化 :
_WaitSet移除该线程。_EntryList(或_cxq)增加该线程。
7️⃣ 被 notify() 唤醒的线程重新获得锁 → 从 BLOCKED 回到 RUNNABLE
- 前提 :线程已在
_EntryList(或_cxq)中等待锁。 - 操作 :当锁再次被释放且该线程被选为唤醒对象时,它会尝试 CAS 成为
_owner。 - 状态变化 :
BLOCKED→RUNNABLE(持有锁)。 - 结构变化 :线程从
_EntryList(或_cxq)中移除,_owner指向该线程。
8️⃣ 线程终止 → TERMINATED
- 前提 :
run()方法结束或异常退出。 - 操作 :如果该线程是
_owner,则释放锁;如果它在任何队列中,则从队列中移除。 - 结构变化:清理相关引用。
三、交互关系图(Mermaid)
stateDiagram-v2
[*] --> RUNNABLE_NoLock : start()
state RUNNABLE_NoLock <>
RUNNABLE_NoLock --> RUNNABLE_Locked : 获得锁\n_owner = current
RUNNABLE_Locked --> WAITING : wait()\n加入 _WaitSet\n释放锁\n_owner = NULL
WAITING --> BLOCKED_EntryList : notify()\n移到 _EntryList
WAITING --> BLOCKED_cxq : notify() (特定策略)\n移到 _cxq
RUNNABLE_Locked --> TERMINATED : 结束
RUNNABLE_NoLock --> BLOCKED_cxq : 锁被占\nCAS 入 _cxq
BLOCKED_cxq --> BLOCKED_EntryList : 锁释放时\n批量转移 _cxq → _EntryList
BLOCKED_EntryList --> RUNNABLE_Locked : 被唤醒并获得锁\n从 _EntryList 移除\n_owner = current
四、核心原则总结
| 状态转换 | 结构变化核心点 |
|---|---|
| 获得锁 | _owner = current |
| 释放锁 | _owner = NULL,并触发 _cxq → _EntryList 转移 |
| 锁竞争失败 | 线程加入 _cxq 头部 |
调用 wait() |
从 _owner → 加入 _WaitSet,_owner 置空 |
调用 notify() |
从 _WaitSet → 移到 _EntryList(或 _cxq) |
| 批量转移 | 锁释放时,若 _EntryList 空,则 _cxq 全体移至 _EntryList(通常反转顺序) |
重要提醒 :
_cxq和_EntryList中的线程状态均为BLOCKED;_WaitSet中的线程状态为WAITING/TIMED_WAITING。_owner始终指向RUNNABLE且持有锁的线程(或 NULL)。
这个交互模型解释了 HotSpot JVM 如何通过分离新竞争者(_cxq)和锁候选者(_EntryList)来优化高并发下的锁性能。