本文将从底层原理和源代码层面详细解释 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 word volatile 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设置自己为owner return; } // 锁被占用,进入entrylist add_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()); // 加入entrylist waiter->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。
到此这篇关于java 的 monitor 机制之从原理与源码解读的文章就介绍到这了,更多相关java monitor原理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论