java重量级锁Monitor

已经理解了 偏向锁 → 轻量级锁 → 重量级锁 的升级过程,现在问到“锁升级后的锁监视器是什么”,这正是 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. 加锁过程(当轻量级锁升级为重量级锁时)

  1. 线程尝试获取轻量级锁失败(CAS 修改对象头失败);
  2. JVM 判断竞争激烈,触发锁膨胀(Lock inflation)
  3. 为该对象创建一个 ObjectMonitor 对象;
  4. 将对象头的 Mark Word 修改为指向这个 Monitor;
  5. 当前线程进入 Monitor 的 _cxq 队列;
  6. 如果 _owner 为空,线程尝试竞争获取锁;
  7. 如果获取失败,线程被 park(阻塞),进入 _EntryList 等待。

✅ 2. 解锁过程

  1. 持有锁的线程执行完 synchronized 块;
  2. JVM 调用 Monitor 的 exit() 方法;
  3. _owner 设置为 null;
  4. _EntryList_cxq 中唤醒一个线程(通过 unpark);
  5. 被唤醒的线程重新竞争锁。


✅ 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_mutexpthread_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 的本质。