Java 线程状态变化与ObjectMonitor之间的关系

下面系统性地总结 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 且不是当前线程。
  • 操作 :
    1. 当前线程被封装为 ObjectWaiter 节点。
    2. 通过 CAS 头插 将节点加入 _cxq(LIFO 栈)。
    3. 可能先自旋,若仍失败则调用 park() 阻塞。
  • 状态 :从 RUNNABLE 变为 BLOCKED。
  • 结构变化 :线程加入 _cxq 头部,_owner 不变。

4️⃣ 锁释放(_owner 退出 synchronized)→ 唤醒 _EntryList 或 _cxq 中的线程

  • 前提 :当前线程是 _owner,即将释放锁。
  • 操作 (以默认 QMode=0 为例):
    1. 如果 _EntryList 非空 → 取出头部线程唤醒。
    2. 如果 _EntryList 为空 → 将 _cxq 中所有线程批量转移到 _EntryList (通常反转顺序,使 LIFO 变 FIFO),然后唤醒 _EntryList 头部线程。
  • 被唤醒的线程 :从 BLOCKED 变为 RUNNABLE(尝试 CAS 成为新 _owner)。
  • 结构变化 :
    • 原 _owner = NULL。
    • _cxq 被清空,_EntryList 获得一批等待线程。

5️⃣ 持有锁的线程调用 Object.wait() → RUNNABLE → WAITING

  • 前提 :当前线程是 _owner。
  • 操作 :
    1. 将当前线程加入 _WaitSet 尾部。
    2. 释放锁:设置 _owner = NULL。
    3. 调用 park() 阻塞当前线程。
  • 状态 :从 RUNNABLE 变为 WAITING。
  • 结构变化 :
    • _owner = NULL。
    • _WaitSet 增加当前线程。
    • 锁释放后,会触发 第 4 步 的唤醒流程。

6️⃣ 其他线程调用 notify() / notifyAll() → 唤醒 _WaitSet 中的线程

  • 前提 :调用 notify() 的线程必须持有同一个对象的锁(即 _owner == 该线程)。
  • 操作 (以 notify() 为例):
    1. 从 _WaitSet 中取出一个线程(通常是头部)。
    2. 将该线程移入 _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 <<fork>> 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)来优化高并发下的锁性能。

相关推荐
mldong8 分钟前
一条审批流的数据库账:5 张核心表、3 张扩展表、0 张表单表
后端·架构
To_OC10 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
前端兰博11 小时前
05-Redis
redis·后端
databook11 小时前
从手动检查到自动监控:一个数据质量工作流的实现
后端·python·数据分析
【JAVA】玩家12 小时前
Spring核心原理全解析:从零到生产实战
java·后端·spring
东风破_12 小时前
从 Neo4j 到 GraphRAG:用 Text-to-Cypher 构建图检索 RAG
人工智能·后端
xiaoqiMikko13 小时前
JVM 线上排查实战(七):jps 看不见它、jstack 连不上它,可它明明活得好好的
java·jvm
llqbzllll14 小时前
Spring AI 工具调用不是反射一下就结束:用 2.0.1 跑通失败恢复与调用上限
人工智能·后端
狼爷14 小时前
Rust/Go/Java/Python/PHP 大比拼:负载下后端框架到底差多少?
java·后端·编程语言
mantou13215 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端