我们深入到 HotSpot 源码层面,详细拆解 ObjectMonitor 的核心属性在加锁、解锁、等待/通知过程中的协作机制。理解这些属性的交互是掌握重量级锁实现的关键。
一、ObjectMonitor 核心属性清单
| 属性 | 类型 | 作用 |
|---|---|---|
_owner |
volatile Thread* |
当前持有锁的线程指针(无锁时为 NULL) |
_recursions |
int |
当前线程的重入次数(_owner 为当前线程时有效) |
_cxq |
ObjectWaiter* |
竞争队列(栈结构,LIFO),新到达的线程先入此队列 |
_EntryList |
ObjectWaiter* |
等待队列(双向链表),存放等待获取锁的线程 |
_WaitSet |
ObjectWaiter* |
等待集(双向链表),存放调用了 wait() 的线程 |
_count |
int |
用于记录 _EntryList 中等待线程数(调试/统计) |
_succ |
volatile Thread* |
启发式优化:暗示下一个可能获得锁的线程,减少不必要的唤醒 |
_Responsible |
Thread* |
用于 try 块或超时等待时的"责任线程" |
_SpinDuration |
int |
自适应自旋的时长基数 |
_WaitSetLock |
int |
保护 _WaitSet 的简单自旋锁 |
本文重点讲解
_owner、_recursions、_cxq、_EntryList、_WaitSet五个核心属性的工作流程。
二、加锁流程(enter 方法)
当线程调用 monitorenter 进入同步块时,最终会调用 ObjectMonitor::enter(TRAPS)。
1. 快速路径(Fast Path)
cpp
Thread * const self = THREAD;
void * cur = Atomic::cmpxchg(&_owner, (void*)NULL, self);
if (cur == NULL) {
// CAS 成功,获得锁
_recursions = 1;
return;
}
if (cur == self) {
// 重入
_recursions++;
return;
}
- 第一步 :尝试 CAS 将
_owner从 NULL 改为当前线程。成功则锁获取完成,_recursions置 1。 - 第二步 :如果
_owner已经是当前线程,则增加_recursions,实现重入。
2. 自旋(Spin)
如果快速路径失败,且 _owner 是其他线程,JVM 会进行自适应自旋(默认开启):
- 通过
TrySpin函数自旋一段时间,期望锁很快释放。 - 自旋过程中如果
_owner变为 NULL 且 CAS 成功,则获得锁。 - 自旋成功可避免线程阻塞/唤醒的开销。
3. 慢速路径(Slow Path)------ 入队与阻塞
如果自旋仍未获得锁,线程进入入队阻塞流程:
cpp
ObjectWaiter node(self); // 封装当前线程
node._TState = ObjectWaiter::TS_CXQ; // 状态:在 cxq 中
// 将 node 放入 _cxq 头部(栈操作)
for (;;) {
node._next = _cxq;
if (Atomic::cmpxchg(&_cxq, node._next, &node) == node._next)
break;
}
- 每个等待锁的线程被封装为
ObjectWaiter节点。 - 新节点总是插入
_cxq的头部(LIFO),这样做是为了让新来的线程更有可能在锁释放时被唤醒,减少饥饿。 - 放入
_cxq后,线程调用park()挂起,进入 BLOCKED 状态。
为什么需要
_cxq和_EntryList两个队列?
_cxq是新竞争线程的"快速入口",采用 LIFO 减少 CAS 冲突。_EntryList是_cxq的"稳态"队列,用于批量转移和唤醒。- 解锁时可以根据策略从
_cxq或_EntryList中唤醒线程,实现不同的公平性/吞吐量权衡。
三、解锁流程(exit 方法)
当线程执行 monitorexit 时,调用 ObjectMonitor::exit(TRAPS)。
1. 减少重入计数
cpp
if (_recursions != 0) {
_recursions--; // 还有重入,不释放锁
return;
}
- 如果
_recursions > 0,仅递减计数,锁仍然被当前线程持有。
2. 释放锁并唤醒下一个线程
当 _recursions == 0 时,真正释放锁:
cpp
OrderAccess::release_store(&_owner, (void*)NULL); // 1. 清空 owner
OrderAccess::fence(); // 2. 写屏障,保证可见性
// 3. 如果 _cxq 或 _EntryList 不为空,需要唤醒后继线程
if (_EntryList != NULL || _cxq != NULL) {
// 根据 QMode 策略选择从哪个队列取线程
ObjectWaiter *w = NULL;
if (QMode == 1) {
// 将 _cxq 全部转移到 _EntryList 尾部,再从 _EntryList 头部唤醒
w = _EntryList;
} else if (QMode == 2) {
// 直接从 _cxq 头部唤醒(LIFO,后进先出)
w = _cxq;
}
// ... 其他 QMode 值(0,3,4)有不同策略
if (w != NULL) {
// 将 w 从队列中取出,调用 unpark() 唤醒该线程
unpark(w->_thread);
}
}
关键点:
QMode是 JVM 参数(-XX:QMode=0/1/2/3/4),控制唤醒策略:QMode=0(默认):先尝试从_EntryList唤醒,如果为空则从_cxq取并移入_EntryList。QMode=1:将_cxq整体转移到_EntryList尾部,再唤醒_EntryList头部(FIFO,较公平)。QMode=2:直接从_cxq头部唤醒(LIFO,新来的线程先获得锁,可能导致饥饿)。
- 唤醒的线程会从之前
park()的位置恢复执行,重新尝试 CAS 获取锁。
四、wait/notify 机制与 _WaitSet
_WaitSet 负责管理调用了 wait() 的线程。
1. wait() 流程(ObjectMonitor::wait)
cpp
// 前提:当前线程持有锁(_owner == self)
_recursions++; // 保存重入计数
int save_recursions = _recursions;
// 释放锁:将 _owner = NULL,并且唤醒 _EntryList/_cxq 中的等待者
exit(true, self); // 释放锁
// 将当前线程封装为 ObjectWaiter,状态设为 TS_WAIT
ObjectWaiter node(self, ObjectWaiter::TS_WAIT);
// 插入 _WaitSet 链表尾部
_WaitSetLock.lock();
node._next = _WaitSet;
_WaitSet = &node;
_WaitSetLock.unlock();
// 挂起当前线程(通过 park())
self->park();
// 当被 notify 或 超时/中断 唤醒后,重新竞争锁
// 重新执行 enter() 获取锁,恢复 _recursions
_owner = self;
_recursions = save_recursions;
- 线程进入
_WaitSet后,不再参与锁竞争,直到被notify/notifyAll移出。 wait()会释放锁,让其他线程有机会进入同步块。
2. notify() 流程(ObjectMonitor::notify)
cpp
// 从 _WaitSet 头部取出一个节点
ObjectWaiter *w = _WaitSet;
if (w != NULL) {
// 将 w 从 _WaitSet 中移除
_WaitSet = w->_next;
// 修改状态为 TS_ENTER,准备参与锁竞争
w->_TState = ObjectWaiter::TS_ENTER;
// 将 w 插入 _cxq 头部(LIFO)或 _EntryList 尾部,取决于 QMode
// 默认插入 _cxq,让新唤醒的线程以 LIFO 方式竞争
w->_next = _cxq;
_cxq = w;
}
notify仅将线程从_WaitSet移到竞争队列(_cxq或_EntryList),不会立即唤醒它。- 被移动的线程将在锁释放后有机会被
unpark()唤醒并重新竞争锁。
五、属性交互全景图
graph TD
A[新竞争线程] -->|enter| B{快速CAS抢锁?}
B -->|成功| C[_owner = thread, _recursions=1]
B -->|失败| D{重入?}
D -->|是| E[_recursions++]
D -->|否| F[自适应自旋]
F -->|成功| C
F -->|失败| G[封装为ObjectWaiter
插入_cxq头部] G --> H[park 阻塞] H --> I[等待被 unpark] J[解锁线程] -->|exit| K[_recursions--] K -->|仍有重入| L[返回] K -->|归零| M[清空 _owner] M --> N[根据QMode选择
从_cxq或_EntryList取节点] N --> O[unpark 唤醒线程] O --> H P[线程调用 wait] --> Q[保存 _recursions
释放锁 exit] Q --> R[插入 _WaitSet] R --> S[park 等待 notify] T[线程调用 notify] --> U[从 _WaitSet 取出节点] U --> V[节点移入 _cxq 或 _EntryList] V --> W[后续被 exit 唤醒]
插入_cxq头部] G --> H[park 阻塞] H --> I[等待被 unpark] J[解锁线程] -->|exit| K[_recursions--] K -->|仍有重入| L[返回] K -->|归零| M[清空 _owner] M --> N[根据QMode选择
从_cxq或_EntryList取节点] N --> O[unpark 唤醒线程] O --> H P[线程调用 wait] --> Q[保存 _recursions
释放锁 exit] Q --> R[插入 _WaitSet] R --> S[park 等待 notify] T[线程调用 notify] --> U[从 _WaitSet 取出节点] U --> V[节点移入 _cxq 或 _EntryList] V --> W[后续被 exit 唤醒]
六、总结:核心属性的职责划分
| 属性 | 核心职责 | 典型工作场景 |
|---|---|---|
_owner |
标识锁的当前持有者 | 加锁时 CAS 设置,解锁时清空 |
_recursions |
支持可重入 | 同一线程多次 enter 时递增,exit 时递减 |
_cxq |
新竞争线程的快速入口 | 慢速路径入队,解锁时按策略取出 |
_EntryList |
等待线程的稳态队列 | 配合 QMode 实现公平或吞吐优先的唤醒 |
_WaitSet |
管理 wait() 的线程 |
wait 时入队,notify 时移出到 _cxq/_EntryList |
这些属性协同工作,既保证了锁的互斥语义,又通过双队列(_cxq + _EntryList)和 QMode 策略在公平性 与吞吐量 之间做了权衡。理解这套机制,就能明白为什么重量级锁在竞争激烈时依然能保持稳定,以及 wait/notify 必须与 synchronized 配合使用的底层原因。