已经理解了 偏向锁 → 轻量级锁 → 重量级锁 的升级过程,现在问到“锁升级后的锁监视器是什么”,这正是 Java 多线程底层的核心机制之一。
✅ 一句话回答:
“锁升级后的‘锁监视器’指的是 JVM 中的 Monitor(管程),它是实现重量级锁的核心数据结构,由 C++ 实现,用于管理线程的阻塞、唤醒和互斥访问。”
下面我们来深入剖析:Monitor 是什么、它怎么工作、为什么叫“监视器”、以及它在锁升级后扮演什么角色。
一、什么是 Monitor(监视器 / 管程)?
📚 定义:
Monitor(监视器) 是一种同步原语(synchronization primitive),用于实现线程互斥和线程间通信。它保证同一时刻只有一个线程可以执行临界区代码。
在 Java 中,每个 Java 对象都可以作为锁(synchronized 锁),而这个锁的背后,就是由 Monitor 来支撑的。
二、Monitor 的结构(HotSpot JVM 实现)
Monitor 是 C++ 对象,定义在 ObjectMonitor.hpp
中,主要包含以下几个关键组件:
┌─────────────────┐
│ ObjectMonitor │ ← Monitor 对象(C++)
├─────────────────┤
│ _header │ ← 保存对象的原始 Mark Word
│ _owner │ ← 指向当前持有锁的线程
│ _WaitSet │ ← 调用 wait() 的线程队列
│ _EntryList │ ← 竞争锁但被阻塞的线程队列
│ _cxq │ ← 竞争队列(Contention Queue)
│ _count │ ← 持有锁的次数(可重入)
└─────────────────┘
▲
│
堆中 Java 对象 ────────────────→ │
┌─────────────┐ │
│ Object │ │
│ ┌─────────┐ │ │
│ │ Mark Word ├─→ 指向 Monitor │
│ └─────────┘ │ │
└─────────────┘ │
│
JVM C++ 层(ObjectMonitor)
🔍 各字段含义:
字段 | 作用 |
---|---|
_owner | 当前持有锁的线程(Thread*) |
_EntryList | 阻塞等待获取锁的线程队列(就绪队列) |
_WaitSet | 调用了 wait() 方法的线程队列(等待队列) |
_cxq | 竞争队列,新来的竞争线程先放这里 |
_count | 锁的重入次数 |
_header | 备份对象原来的 Mark Word |
三、Monitor 是如何工作的?(加锁 & 解锁)
✅ 1. 加锁过程(当轻量级锁升级为重量级锁时)
- 线程尝试获取轻量级锁失败(CAS 修改对象头失败);
- JVM 判断竞争激烈,触发锁膨胀(Lock inflation);
- 为该对象创建一个
ObjectMonitor
对象; - 将对象头的 Mark Word 修改为指向这个 Monitor;
- 当前线程进入 Monitor 的
_cxq
队列; - 如果
_owner
为空,线程尝试竞争获取锁; - 如果获取失败,线程被 park(阻塞),进入
_EntryList
等待。
✅ 2. 解锁过程
- 持有锁的线程执行完
synchronized
块; - JVM 调用 Monitor 的
exit()
方法; _owner
设置为 null;- 从
_EntryList
或_cxq
中唤醒一个线程(通过unpark
); - 被唤醒的线程重新竞争锁。
✅ 3. wait()
/ notify()
的实现
Monitor 不仅管理锁,还支持线程通信:
wait()
:当前线程释放锁,进入_WaitSet
,并被阻塞;notify()
:从_WaitSet
中唤醒一个线程,移到_EntryList
等待竞争锁;notifyAll()
:唤醒所有在_WaitSet
中的线程。
synchronized (obj) {
obj.wait(); // 线程进入 Monitor 的 _WaitSet
}
四、Monitor 和对象头的关系
锁状态 | 对象头(Mark Word)内容 |
---|---|
无锁 | 哈希码、分代年龄等 |
偏向锁 | 线程 ID、偏向时间戳 |
轻量级锁 | 指向栈中 Lock Record 的指针 |
重量级锁 | 指向 ObjectMonitor 的指针 |
🔥 当锁升级为重量级锁后,对象头不再指向栈帧,而是指向堆中的 Monitor 对象。
五、Monitor 是“重量级”的原因
- 依赖操作系统:线程阻塞/唤醒使用
pthread_mutex
、pthread_cond
等系统调用; - 上下文切换开销大:线程从用户态进入内核态,成本高;
- 需要额外内存:每个 Monitor 占用几百字节内存;
- 队列管理复杂:需要维护
_EntryList
、_WaitSet
等。
这就是为什么我们说 “重量级锁”很重,要尽量避免进入。
六、如何观察 Monitor?
虽然 Monitor 是 JVM 内部实现,但我们可以通过一些工具间接观察:
1. 使用 jstack
查看线程状态
jstack <pid>
输出中会看到:
- waiting to lock <0x000000076b7a8960> (a java.lang.Object)
- locked <0x000000076b7a8960> (a java.lang.Object)
这个 0x000000076b7a8960
就是对象地址,背后对应一个 Monitor。
2. 使用 JOL(Java Object Layout)查看对象头
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
输出中可以看到 Mark Word 的值,如果是指针,很可能指向 Monitor。
七、总结:Monitor 的核心作用
作用 | 说明 |
---|---|
🔒 互斥(Mutex) | 保证同一时刻只有一个线程持有锁(通过 _owner ) |
🔄 阻塞与唤醒 | 竞争失败的线程进入 _EntryList 被阻塞,由操作系统调度 |
🗣️ 线程通信 | 支持 wait() 、notify() 、notifyAll() (通过 _WaitSet ) |
🔁 可重入 | 记录 _count ,支持同一个线程多次获取同一把锁 |
✅ 一句话总结:
“Monitor 是 synchronized 的幕后管家, 管线程、管锁、管等待; 对象头一指它, 阻塞唤醒全靠它; 虽然重量有点大, 但保线程安全不落下。”
你现在理解的,正是 Java 多线程同步的底层基石。掌握 Monitor,你就真正理解了 synchronized
的本质。