Java 的 Monitor 机制:原理与源码详解
本文将从底层原理和源代码层面详细解释 Java 的 Monitor 机制,尽量用通俗易懂的语言让初学者也能理解。从概念开始,逐步深入到实现细节,涵盖 Monitor 的作用、结构、源码分析,并提供完整的步骤和推导。由于 Monitor 是 Java 锁机制(尤其是 synchronized)的核心,我会结合对象头和锁的场景增强理解。
一、什么是 Java 的 Monitor?为什么需要它?
1.1 Monitor 的通俗概念
Monitor(监视器)是 Java 中实现线程同步的核心机制,可以看作一个“独占门锁”。想象一个只有一张票的电影院(共享资源),Monitor 就像检票员,确保同一时间只有一个观众(线程)能进去看电影,其他人得在门口排队等着。
在 Java 中,Monitor 是 synchronized 关键字的底层实现,用于保证多线程访问共享资源时的线程安全。每个 Java 对象都可以关联一个 Monitor,充当锁的角色。
1.2 为什么需要 Monitor?
在多线程编程中,多个线程可能同时访问共享资源(比如一个变量或对象),如果没有协调机制,会导致数据不一致。例如:
public class Counter {private int count = 0;public void increment() {count++; // 看似简单,实际包含读、加、写三步}
}
count++
包含三个步骤:
- 读取
count
的值。 - 加 1。
- 写回新值。
如果两个线程同时执行 increment(),可能出现:
- 线程 A 读取
count = 0
。 - 线程 B 读取
count = 0
。 - 线程 A 计算
0 + 1 = 1
,写回count = 1
。 - 线程 B 计算
0 + 1 = 1
,写回count = 1
。
结果是两次加操作后,count
仍为 1,而不是 2。Monitor 通过确保同一时间只有一个线程执行关键代码(临界区),解决了这个问题。
通俗解释:
- Monitor 像一个“排队系统”,确保只有一个线程能“进门”操作共享资源,其他线程在外面等着。
二、Monitor 在 Java 中的作用
Monitor 是 synchronized 关键字的底层实现,负责:
- 互斥访问:同一时间只有一个线程能持有 Monitor,进入临界区。
- 线程协调:支持线程的等待和唤醒(通过 wait()、notify()、notifyAll())。
- 锁状态管理:记录锁的持有者、等待队列等。
Monitor 与对象头紧密相关,对象头的 Mark Word 在重量级锁状态下会指向 Monitor 实例。
三、Monitor 的工作原理
3.1 Monitor 的核心组件
Monitor 是一个操作系统级别的同步工具,通常包含以下部分:
- Owner(持有者):当前持有锁的线程。
- Entry Set(进入队列):等待获取锁的线程队列。
- Wait Set(等待队列):调用 wait() 后释放锁并等待的线程。
- Recursions(重入计数):记录同一线程重入锁的次数(支持可重入锁)。
通俗例子:
- 想象一个厕所(共享资源),只有一个坑位,Monitor 是门上的智能锁:
- Owner:正在用厕所的人(当前线程)。
- Entry Set:在门口排队等坑位的人(等待锁的线程)。
- Wait Set:暂时出去喝咖啡、等着被叫回来的人(调用 wait() 的线程)。
- Recursions:记录同一个人反复进出厕所的次数(重入锁)。
3.2 Monitor 的状态转换
Monitor 的工作涉及以下状态和操作:
- 获取锁(Enter):
- 线程尝试进入 Monitor。
- 如果 Monitor 空闲,线程成为 Owner。
- 如果已被占用,线程进入 Entry Set 等待。
- 执行临界区:Owner 线程执行 synchronized 代码。
- 释放锁(Exit):
- Owner 线程退出临界区,释放 Monitor。
- 唤醒 Entry Set 中的一个线程(或 Wait Set 中的线程,如果有 notify())。
- 等待和唤醒:
- 线程调用 wait(),释放 Monitor,进入 Wait Set。
- 其他线程调用 notify() 或 notifyAll(),唤醒 Wait Set 中的线程。
四、Monitor 的底层实现
Monitor 的实现主要在 HotSpot JVM 的 C++ 源码中,核心类是 ObjectMonitor,位于 src/hotspot/share/runtime/objectMonitor.hpp。以下从源码和原理逐步分析。
4.1 ObjectMonitor 的结构
ObjectMonitor 是 HotSpot JVM 中 Monitor 的具体实现,包含以下关键字段(简化版):
class ObjectMonitor {
private:volatile Thread* _owner; // 当前持有锁的线程volatile intptr_t _recursions; // 重入次数ObjectWaiter* _EntryList; // 等待锁的线程队列(Entry Set)ObjectWaiter* _WaitSet; // 等待notify的线程队列(Wait Set)markOop _header; // 保存对象的Mark Wordvolatile int _count; // 等待线程数volatile int _waiters; // Wait Set中的线程数// ...
};
字段解释:
- _owner:指向当前持有 Monitor 的线程。如果为空,表示 Monitor 空闲。
- _recursions:记录重入次数。例如,线程多次进入同一 synchronized 块,计数加 1。
- _EntryList:一个链表,存储等待获取锁的线程(阻塞状态)。
- _WaitSet:一个链表,存储调用 wait() 的线程(等待被唤醒)。
- _header:保存对象头的 Mark Word,锁释放时恢复。
- _count:Entry Set 中的线程数,用于优化。
- _waiters:Wait Set 中的线程数,用于管理等待线程。
通俗解释:
- ObjectMonitor 像一个智能门锁的控制面板,记录“谁在用”(Owner)、“谁在排队”(EntryList)、“谁在休息”(WaitSet)。
4.2 Monitor 的工作流程
以下是 Monitor 的核心操作流程,结合源码分析:
4.2.1 获取锁(enter)
当线程执行 synchronized 代码时,JVM 调用 ObjectMonitor::enter:
void ObjectMonitor::enter(Thread* self) {if (_owner == self) {_recursions++; // 重入,增加计数return;}if (_owner == nullptr && Atomic::cmpxchg(self, &_owner, nullptr) == nullptr) {// 无人持有,CAS设置自己为Ownerreturn;}// 锁被占用,进入EntryListadd_to_entry_list(self);self->park(); // 线程阻塞
}
步骤:
- 检查重入:
- 如果当前线程已经是 _owner,说明是重入锁,增加 _recursions 计数,直接返回。
- 通俗解释:如果厕所里的人是你自己,就不用再排队,直接继续用。
- 尝试获取空闲锁:
- 如果 _owner 为空,用 CAS(Compare-And-Swap)原子操作设置 _owner 为当前线程。
- CAS 确保多线程竞争时只有一个线程成功。
- 通俗解释:如果厕所没人,赶紧把门锁上,标上“我的名字”。
- 进入等待队列:
- 如果锁被占用,线程加入 _EntryList,调用 park() 阻塞自己。
- 通俗解释:如果有人在用,就去门口排队,暂时“睡着”。
4.2.2 释放锁(exit)
当线程退出 synchronized 块时,JVM 调用 ObjectMonitor::exit:
void ObjectMonitor::exit(Thread* self) {if (_recursions > 0) {_recursions--; // 减少重入计数return;}if (_owner != self) {return; // 非Owner线程不能释放}_owner = nullptr; // 释放锁if (_EntryList != nullptr || _WaitSet != nullptr) {notify_waiters(); // 唤醒等待线程}
}
步骤:
- 检查重入:
- 如果 _recursions 大于 0,减少计数,不释放锁。
- 通俗解释:你还没完全“离开”厕所,只是少用了一次。
- 验证 Owner:
- 确保当前线程是 _owner,防止非法释放。
- 通俗解释:只有用厕所的人才能开锁。
- 释放锁:
- 设置 _owner 为 nullptr,表示锁空闲。
- 通俗解释:把门锁打开,标上“没人用”。
- 唤醒等待线程:
- 如果 _EntryList 或 _WaitSet 不为空,唤醒一个或多个线程(通过 unpark())。
- 通俗解释:喊一声“下一个”,让排队的人进来。
4.2.3 等待(wait)
当线程调用 Object.wait() 时,JVM 调用 ObjectMonitor::wait:
void ObjectMonitor::wait(Thread* self, jlong millis) {if (_owner != self) {return; // 非Owner不能wait}// 保存状态ObjectWaiter node(self);_WaitSet->append(&node); // 加入Wait Set_recursions = 0; // 重置重入计数exit(self); // 释放锁self->park(millis); // 阻塞等待
}
步骤:
- 验证 Owner:
- 确保当前线程是 _owner,因为只有锁持有者能调用 wait()。
- 通俗解释:只有在厕所里的人才能说“我先出去等会儿”。
- 加入 Wait Set:
- 创建一个 ObjectWaiter 节点,加入 _WaitSet。
- 通俗解释:把名字写在“休息区”名单上。
- 释放锁:
- 重置 _recursions,调用 exit() 释放锁。
- 通俗解释:开门出去,让别人用厕所。
- 阻塞线程:
- 调用 park(millis),线程阻塞(如果指定了超时时间 millis)。
- 通俗解释:去旁边“睡着”,等着被叫醒。
4.2.4 唤醒(notify/notifyAll)
当线程调用 Object.notify() 或 notifyAll() 时,JVM 调用 ObjectMonitor::notify:
void ObjectMonitor::notify(TRAPS) {if (_WaitSet == nullptr) {return; // 没有等待线程}ObjectWaiter* waiter = _WaitSet->remove_first(); // 取出一个线程add_to_entry_list(waiter->thread()); // 加入EntryListwaiter->thread()->unpark(); // 唤醒线程
}void ObjectMonitor::notifyAll(TRAPS) {while (_WaitSet != nullptr) {notify(); // 逐个唤醒}
}
步骤:
- 检查 Wait Set:
- 如果 _WaitSet 为空,直接返回。
- 通俗解释:如果休息区没人,不用喊。
- 唤醒线程(notify):
- 从 _WaitSet 取出一个线程,加入 _EntryList,调用 unpark() 唤醒。
- 通俗解释:叫醒休息区的一个人,让他去排队抢锁。
- 唤醒所有线程(notifyAll):
- 循环调用 notify(),唤醒 _WaitSet 中所有线程。
- 通俗解释:喊“大家都回来排队”,让休息区所有人去抢锁。
4.3 Monitor 与对象头的交互
Monitor 与对象头的 Mark Word 紧密相关:
- 当锁升级为重量级锁,Mark Word 存储指向 ObjectMonitor 的指针(锁标志位为 10)。
- ObjectMonitor 的 _header 字段保存原始 Mark Word(包括哈希码、GC 年龄等)。
- 释放锁时,JVM 将 _header 恢复到对象头的 Mark Word。
通俗解释:
- Mark Word 像门上的“地址牌”,重量级锁时指向“保安室”(Monitor)。
- Monitor 像保安室,记录门的原始信息(_header),解锁时把地址牌换回去。
4.4 字节码层面的支持
synchronized 代码被编译为字节码指令 monitorenter 和 monitorexit:
synchronized(obj) {count++;
}
字节码(简化):
monitorenter // 获取Monitor
iload count
iadd 1
istore count
monitorexit // 释放Monitor
- monitorenter:调用 ObjectMonitor::enter,尝试获取锁。
- monitorexit:调用 ObjectMonitor::exit,释放锁。
五、Monitor 的底层操作系统支持
Monitor 的阻塞和唤醒依赖操作系统的线程调度机制:
- park/unpark:
- park():阻塞线程,底层调用操作系统的 pthread_cond_wait(Linux)或类似机制。
- unpark():唤醒线程,底层调用 pthread_cond_signal 或 futex(Linux)。
- 互斥锁(Mutex):
- Monitor 的互斥性通过操作系统的 pthread_mutex(Linux)或类似机制实现。
- CAS 操作(Atomic::cmpxchg)依赖 CPU 的原子指令(如 cmpxchg)。
通俗解释:
- Monitor 像一个“智能门锁”,但真正的“锁芯”是操作系统提供的。
- 线程“睡着”或“醒来”靠操作系统调度,就像保安喊“下一个”。
六、Monitor 的优化
JVM 对 Monitor 进行了大量优化,减少性能开销:
- 偏向锁:
- 如果锁通常被同一线程持有,Mark Word 记录线程 ID,避免创建 Monitor。
- 通俗解释:给常来的人发“VIP卡”,不用每次找保安。
- 轻量级锁:
- 低竞争时,使用 CAS 操作锁记录,不创建 Monitor。
- 通俗解释:用“临时钥匙”代替保安,速度更快。
- 重量级锁:
- 高竞争时才使用 Monitor,依赖操作系统。
- 通俗解释:人太多,只能请保安(Monitor)来管秩序。
- 自旋锁:
- 线程在
park()
前可能短暂自旋(循环尝试),避免立即阻塞。 - 通俗解释:排队时先看一眼门开了没,省得直接睡着。
- 线程在
这些优化通过对象头的 Mark Word 动态切换锁状态(无锁 → 偏向锁 → 轻量级锁 → 重量级锁)。
七、完整推导流程
-
问题背景:
- 多线程并发访问共享资源可能导致数据不一致,需通过锁保证线程安全。
- Monitor 是 synchronized 的核心实现,管理互斥和线程协调。
-
Monitor 的结构:
- 包含 Owner、EntryList、WaitSet、Recursions 等,记录锁状态和线程队列。
- 与对象头的 Mark Word 交互,重量级锁时存储 Monitor 指针。
-
工作流程:
- 获取锁:检查重入、尝试 CAS 获取、或加入 EntryList 阻塞。
- 释放锁:减少重入计数、置空 Owner、唤醒等待线程。
- 等待/唤醒:通过 Wait Set 实现 wait() 和 notify(),涉及锁释放和重新获取。
-
底层实现:
- ObjectMonitor 类管理 Monitor 逻辑,源码在 objectMonitor.cpp。
- 依赖操作系统 Mutex 和线程调度(park/unpark)。
-
优化:
- 偏向锁、轻量级锁减少 Monitor 使用,重量级锁作为 fallback。
- 自旋锁和 CAS 提高效率。
八、通俗总结
- Monitor 是什么?它是 synchronized 的“门锁”,确保线程排队访问共享资源。
- 怎么工作?像一个智能保安,记录“谁在用”(Owner)、“谁在等”(EntryList/WaitSet),支持等待和唤醒。
- 为什么重要?没有 Monitor,synchronized 无法实现线程安全和协调。
- 底层实现?通过 HotSpot JVM 的 ObjectMonitor 类,依赖操作系统互斥锁和调度。
生活化比喻:
- Monitor 像一个厕所的智能门锁:
- 有人用时,标上“占用”(Owner)。
- 有人排队时,记下名单(EntryList)。
- 有人出去喝咖啡时,记在休息区(WaitSet)。
- 支持“熟客”反复进出(重入),还能喊人回来(notify)。
九、源码分析补充
以下是 ObjectMonitor 的关键方法(伪代码简化版),进一步说明逻辑:
// 获取锁
void ObjectMonitor::enter(Thread* self) {if (_owner == self) {_recursions++;return;}if (_owner == nullptr && Atomic::cmpxchg(self, &_owner, nullptr) == nullptr) {return;}add_to_entry_list(self);self->park();
}// 释放锁
void ObjectMonitor::exit(Thread* self) {if (_recursions > 0) {_recursions--;return;}_owner = nullptr;if (_EntryList || _WaitSet) {notify_waiters();}
}// 等待
void ObjectMonitor::wait(Thread* self, jlong millis) {_WaitSet->append(self);_recursions = 0;exit(self);self->park(millis);
}// 唤醒
void ObjectMonitor::notify() {if (_WaitSet) {Thread* t = _WaitSet->remove_first();_EntryList->append(t);t->unpark();}
}
关键点:
- CAS:确保 _owner 设置的原子性。
- park/unpark:依赖 LockSupport,底层调用操作系统调度。
- 队列管理:
ObjectWaiter
节点维护 EntryList 和 WaitSet。
十、扩展阅读
- 源码推荐:
- HotSpot JVM:objectMonitor.hpp 和 objectMonitor.cpp(Monitor 实现)。
- 相关文件:synchronizer.cpp(锁状态切换)、markOop.hpp(Mark Word)。
- 工具:
- 用 jstack 查看线程状态,分析 Monitor 竞争。
- 用 JOL(Java Object Layout)查看对象头和 Monitor 指针。
- 书籍:
- 《深入理解 Java 虚拟机》(周志明):讲解 JVM 锁和 Monitor 实现。
- 《Java 并发编程实战》:结合 synchronized 理解 Monitor。