Java 的 Monitor 機(jī)制之從原理與源碼解讀
本文將從底層原理和源代碼層面詳細(xì)解釋 Java 的 Monitor 機(jī)制,盡量用通俗易懂的語言讓初學(xué)者也能理解。從概念開始,逐步深入到實(shí)現(xiàn)細(xì)節(jié),涵蓋 Monitor 的作用、結(jié)構(gòu)、源碼分析,并提供完整的步驟和推導(dǎo)。由于 Monitor 是 Java 鎖機(jī)制(尤其是 synchronized)的核心,我會(huì)結(jié)合對(duì)象頭和鎖的場(chǎng)景增強(qiáng)理解。
一、什么是 Java 的 Monitor?為什么需要它?
1.1 Monitor 的通俗概念
Monitor(監(jiān)視器)是 Java 中實(shí)現(xiàn)線程同步的核心機(jī)制,可以看作一個(gè)“獨(dú)占門鎖”。想象一個(gè)只有一張票的電影院(共享資源),Monitor 就像檢票員,確保同一時(shí)間只有一個(gè)觀眾(線程)能進(jìn)去看電影,其他人得在門口排隊(duì)等著。
在 Java 中,Monitor 是 synchronized 關(guān)鍵字的底層實(shí)現(xiàn),用于保證多線程訪問共享資源時(shí)的線程安全。每個(gè) Java 對(duì)象都可以關(guān)聯(lián)一個(gè) Monitor,充當(dāng)鎖的角色。
1.2 為什么需要 Monitor?
在多線程編程中,多個(gè)線程可能同時(shí)訪問共享資源(比如一個(gè)變量或?qū)ο螅绻麤]有協(xié)調(diào)機(jī)制,會(huì)導(dǎo)致數(shù)據(jù)不一致。例如:
public class Counter {
private int count = 0;
public void increment() {
count++; // 看似簡單,實(shí)際包含讀、加、寫三步
}
}count++ 包含三個(gè)步驟:
- 讀取
count的值。 - 加 1。
- 寫回新值。
如果兩個(gè)線程同時(shí)執(zhí)行 increment(),可能出現(xiàn):
- 線程 A 讀取
count = 0。 - 線程 B 讀取
count = 0。 - 線程 A 計(jì)算
0 + 1 = 1,寫回count = 1。 - 線程 B 計(jì)算
0 + 1 = 1,寫回count = 1。
結(jié)果是兩次加操作后,count 仍為 1,而不是 2。Monitor 通過確保同一時(shí)間只有一個(gè)線程執(zhí)行關(guān)鍵代碼(臨界區(qū)),解決了這個(gè)問題。
通俗解釋:
- Monitor 像一個(gè)“排隊(duì)系統(tǒng)”,確保只有一個(gè)線程能“進(jìn)門”操作共享資源,其他線程在外面等著。
二、Monitor 在 Java 中的作用
Monitor 是 synchronized 關(guān)鍵字的底層實(shí)現(xiàn),負(fù)責(zé):
- 互斥訪問:同一時(shí)間只有一個(gè)線程能持有 Monitor,進(jìn)入臨界區(qū)。
- 線程協(xié)調(diào):支持線程的等待和喚醒(通過 wait()、notify()、notifyAll())。
- 鎖狀態(tài)管理:記錄鎖的持有者、等待隊(duì)列等。
Monitor 與對(duì)象頭緊密相關(guān),對(duì)象頭的 Mark Word 在重量級(jí)鎖狀態(tài)下會(huì)指向 Monitor 實(shí)例。
三、Monitor 的工作原理
3.1 Monitor 的核心組件
Monitor 是一個(gè)操作系統(tǒng)級(jí)別的同步工具,通常包含以下部分:
- Owner(持有者):當(dāng)前持有鎖的線程。
- Entry Set(進(jìn)入隊(duì)列):等待獲取鎖的線程隊(duì)列。
- Wait Set(等待隊(duì)列):調(diào)用 wait() 后釋放鎖并等待的線程。
- Recursions(重入計(jì)數(shù)):記錄同一線程重入鎖的次數(shù)(支持可重入鎖)。
通俗例子:
- 想象一個(gè)廁所(共享資源),只有一個(gè)坑位,Monitor 是門上的智能鎖:
- Owner:正在用廁所的人(當(dāng)前線程)。
- Entry Set:在門口排隊(duì)等坑位的人(等待鎖的線程)。
- Wait Set:暫時(shí)出去喝咖啡、等著被叫回來的人(調(diào)用 wait() 的線程)。
- Recursions:記錄同一個(gè)人反復(fù)進(jìn)出廁所的次數(shù)(重入鎖)。
3.2 Monitor 的狀態(tài)轉(zhuǎn)換
Monitor 的工作涉及以下狀態(tài)和操作:
- 獲取鎖(Enter):
- 線程嘗試進(jìn)入 Monitor。
- 如果 Monitor 空閑,線程成為 Owner。
- 如果已被占用,線程進(jìn)入 Entry Set 等待。
- 執(zhí)行臨界區(qū):Owner 線程執(zhí)行 synchronized 代碼。
- 釋放鎖(Exit):
- Owner 線程退出臨界區(qū),釋放 Monitor。
- 喚醒 Entry Set 中的一個(gè)線程(或 Wait Set 中的線程,如果有 notify())。
- 等待和喚醒:
- 線程調(diào)用 wait(),釋放 Monitor,進(jìn)入 Wait Set。
- 其他線程調(diào)用 notify() 或 notifyAll(),喚醒 Wait Set 中的線程。
四、Monitor 的底層實(shí)現(xiàn)
Monitor 的實(shí)現(xiàn)主要在 HotSpot JVM 的 C++ 源碼中,核心類是 ObjectMonitor,位于 src/hotspot/share/runtime/objectMonitor.hpp。以下從源碼和原理逐步分析。
4.1 ObjectMonitor 的結(jié)構(gòu)
ObjectMonitor 是 HotSpot JVM 中 Monitor 的具體實(shí)現(xiàn),包含以下關(guān)鍵字段(簡化版):
class ObjectMonitor {
private:
volatile Thread* _owner; // 當(dāng)前持有鎖的線程
volatile intptr_t _recursions; // 重入次數(shù)
ObjectWaiter* _EntryList; // 等待鎖的線程隊(duì)列(Entry Set)
ObjectWaiter* _WaitSet; // 等待notify的線程隊(duì)列(Wait Set)
markOop _header; // 保存對(duì)象的Mark Word
volatile int _count; // 等待線程數(shù)
volatile int _waiters; // Wait Set中的線程數(shù)
// ...
};字段解釋:
- _owner:指向當(dāng)前持有 Monitor 的線程。如果為空,表示 Monitor 空閑。
- _recursions:記錄重入次數(shù)。例如,線程多次進(jìn)入同一 synchronized 塊,計(jì)數(shù)加 1。
- _EntryList:一個(gè)鏈表,存儲(chǔ)等待獲取鎖的線程(阻塞狀態(tài))。
- _WaitSet:一個(gè)鏈表,存儲(chǔ)調(diào)用 wait() 的線程(等待被喚醒)。
- _header:保存對(duì)象頭的 Mark Word,鎖釋放時(shí)恢復(fù)。
- _count:Entry Set 中的線程數(shù),用于優(yōu)化。
- _waiters:Wait Set 中的線程數(shù),用于管理等待線程。
通俗解釋:
- ObjectMonitor 像一個(gè)智能門鎖的控制面板,記錄“誰在用”(Owner)、“誰在排隊(duì)”(EntryList)、“誰在休息”(WaitSet)。
4.2 Monitor 的工作流程
以下是 Monitor 的核心操作流程,結(jié)合源碼分析:
4.2.1 獲取鎖(enter)
當(dāng)線程執(zhí)行 synchronized 代碼時(shí),JVM 調(diào)用 ObjectMonitor::enter:
void ObjectMonitor::enter(Thread* self) {
if (_owner == self) {
_recursions++; // 重入,增加計(jì)數(shù)
return;
}
if (_owner == nullptr && Atomic::cmpxchg(self, &_owner, nullptr) == nullptr) {
// 無人持有,CAS設(shè)置自己為Owner
return;
}
// 鎖被占用,進(jìn)入EntryList
add_to_entry_list(self);
self->park(); // 線程阻塞
}步驟:
- 檢查重入:
- 如果當(dāng)前線程已經(jīng)是 _owner,說明是重入鎖,增加 _recursions 計(jì)數(shù),直接返回。
- 通俗解釋:如果廁所里的人是你自己,就不用再排隊(duì),直接繼續(xù)用。
- 嘗試獲取空閑鎖:
- 如果 _owner 為空,用 CAS(Compare-And-Swap)原子操作設(shè)置 _owner 為當(dāng)前線程。
- CAS 確保多線程競(jìng)爭時(shí)只有一個(gè)線程成功。
- 通俗解釋:如果廁所沒人,趕緊把門鎖上,標(biāo)上“我的名字”。
- 進(jìn)入等待隊(duì)列:
- 如果鎖被占用,線程加入 _EntryList,調(diào)用 park() 阻塞自己。
- 通俗解釋:如果有人在用,就去門口排隊(duì),暫時(shí)“睡著”。
4.2.2 釋放鎖(exit)
當(dāng)線程退出 synchronized 塊時(shí),JVM 調(diào)用 ObjectMonitor::exit:
void ObjectMonitor::exit(Thread* self) {
if (_recursions > 0) {
_recursions--; // 減少重入計(jì)數(shù)
return;
}
if (_owner != self) {
return; // 非Owner線程不能釋放
}
_owner = nullptr; // 釋放鎖
if (_EntryList != nullptr || _WaitSet != nullptr) {
notify_waiters(); // 喚醒等待線程
}
}步驟:
- 檢查重入:
- 如果 _recursions 大于 0,減少計(jì)數(shù),不釋放鎖。
- 通俗解釋:你還沒完全“離開”廁所,只是少用了一次。
- 驗(yàn)證 Owner:
- 確保當(dāng)前線程是 _owner,防止非法釋放。
- 通俗解釋:只有用廁所的人才能開鎖。
- 釋放鎖:
- 設(shè)置 _owner 為 nullptr,表示鎖空閑。
- 通俗解釋:把門鎖打開,標(biāo)上“沒人用”。
- 喚醒等待線程:
- 如果 _EntryList 或 _WaitSet 不為空,喚醒一個(gè)或多個(gè)線程(通過 unpark())。
- 通俗解釋:喊一聲“下一個(gè)”,讓排隊(duì)的人進(jìn)來。
4.2.3 等待(wait)
當(dāng)線程調(diào)用 Object.wait() 時(shí),JVM 調(diào)用 ObjectMonitor::wait:
void ObjectMonitor::wait(Thread* self, jlong millis) {
if (_owner != self) {
return; // 非Owner不能wait
}
// 保存狀態(tài)
ObjectWaiter node(self);
_WaitSet->append(&node); // 加入Wait Set
_recursions = 0; // 重置重入計(jì)數(shù)
exit(self); // 釋放鎖
self->park(millis); // 阻塞等待
}步驟:
- 驗(yàn)證 Owner:
- 確保當(dāng)前線程是 _owner,因?yàn)橹挥墟i持有者能調(diào)用 wait()。
- 通俗解釋:只有在廁所里的人才能說“我先出去等會(huì)兒”。
- 加入 Wait Set:
- 創(chuàng)建一個(gè) ObjectWaiter 節(jié)點(diǎn),加入 _WaitSet。
- 通俗解釋:把名字寫在“休息區(qū)”名單上。
- 釋放鎖:
- 重置 _recursions,調(diào)用 exit() 釋放鎖。
- 通俗解釋:開門出去,讓別人用廁所。
- 阻塞線程:
- 調(diào)用 park(millis),線程阻塞(如果指定了超時(shí)時(shí)間 millis)。
- 通俗解釋:去旁邊“睡著”,等著被叫醒。
4.2.4 喚醒(notify/notifyAll)
當(dāng)線程調(diào)用 Object.notify() 或 notifyAll() 時(shí),JVM 調(diào)用 ObjectMonitor::notify:
void ObjectMonitor::notify(TRAPS) {
if (_WaitSet == nullptr) {
return; // 沒有等待線程
}
ObjectWaiter* waiter = _WaitSet->remove_first(); // 取出一個(gè)線程
add_to_entry_list(waiter->thread()); // 加入EntryList
waiter->thread()->unpark(); // 喚醒線程
}
void ObjectMonitor::notifyAll(TRAPS) {
while (_WaitSet != nullptr) {
notify(); // 逐個(gè)喚醒
}
}步驟:
- 檢查 Wait Set:
- 如果 _WaitSet 為空,直接返回。
- 通俗解釋:如果休息區(qū)沒人,不用喊。
- 喚醒線程(notify):
- 從 _WaitSet 取出一個(gè)線程,加入 _EntryList,調(diào)用 unpark() 喚醒。
- 通俗解釋:叫醒休息區(qū)的一個(gè)人,讓他去排隊(duì)搶鎖。
- 喚醒所有線程(notifyAll):
- 循環(huán)調(diào)用 notify(),喚醒 _WaitSet 中所有線程。
- 通俗解釋:喊“大家都回來排隊(duì)”,讓休息區(qū)所有人去搶鎖。
4.3 Monitor 與對(duì)象頭的交互
Monitor 與對(duì)象頭的 Mark Word 緊密相關(guān):
- 當(dāng)鎖升級(jí)為重量級(jí)鎖,Mark Word 存儲(chǔ)指向 ObjectMonitor 的指針(鎖標(biāo)志位為 10)。
- ObjectMonitor 的 _header 字段保存原始 Mark Word(包括哈希碼、GC 年齡等)。
- 釋放鎖時(shí),JVM 將 _header 恢復(fù)到對(duì)象頭的 Mark Word。
通俗解釋:
- Mark Word 像門上的“地址牌”,重量級(jí)鎖時(shí)指向“保安室”(Monitor)。
- Monitor 像保安室,記錄門的原始信息(_header),解鎖時(shí)把地址牌換回去。
4.4 字節(jié)碼層面的支持
synchronized 代碼被編譯為字節(jié)碼指令 monitorenter 和 monitorexit:
synchronized(obj) {
count++;
}
字節(jié)碼(簡化):
monitorenter // 獲取Monitor iload count iadd 1 istore count monitorexit // 釋放Monitor
- monitorenter:調(diào)用 ObjectMonitor::enter,嘗試獲取鎖。
- monitorexit:調(diào)用 ObjectMonitor::exit,釋放鎖。
五、Monitor 的底層操作系統(tǒng)支持
Monitor 的阻塞和喚醒依賴操作系統(tǒng)的線程調(diào)度機(jī)制:
- park/unpark:
- park():阻塞線程,底層調(diào)用操作系統(tǒng)的 pthread_cond_wait(Linux)或類似機(jī)制。
- unpark():喚醒線程,底層調(diào)用 pthread_cond_signal 或 futex(Linux)。
- 互斥鎖(Mutex):
- Monitor 的互斥性通過操作系統(tǒng)的 pthread_mutex(Linux)或類似機(jī)制實(shí)現(xiàn)。
- CAS 操作(Atomic::cmpxchg)依賴 CPU 的原子指令(如 cmpxchg)。
通俗解釋:
- Monitor 像一個(gè)“智能門鎖”,但真正的“鎖芯”是操作系統(tǒng)提供的。
- 線程“睡著”或“醒來”靠操作系統(tǒng)調(diào)度,就像保安喊“下一個(gè)”。
六、Monitor 的優(yōu)化
JVM 對(duì) Monitor 進(jìn)行了大量優(yōu)化,減少性能開銷:
- 偏向鎖:
- 如果鎖通常被同一線程持有,Mark Word 記錄線程 ID,避免創(chuàng)建 Monitor。
- 通俗解釋:給常來的人發(fā)“VIP卡”,不用每次找保安。
- 輕量級(jí)鎖:
- 低競(jìng)爭時(shí),使用 CAS 操作鎖記錄,不創(chuàng)建 Monitor。
- 通俗解釋:用“臨時(shí)鑰匙”代替保安,速度更快。
- 重量級(jí)鎖:
- 高競(jìng)爭時(shí)才使用 Monitor,依賴操作系統(tǒng)。
- 通俗解釋:人太多,只能請(qǐng)保安(Monitor)來管秩序。
- 自旋鎖:
- 線程在
park()前可能短暫自旋(循環(huán)嘗試),避免立即阻塞。 - 通俗解釋:排隊(duì)時(shí)先看一眼門開了沒,省得直接睡著。
- 線程在
這些優(yōu)化通過對(duì)象頭的 Mark Word 動(dòng)態(tài)切換鎖狀態(tài)(無鎖 → 偏向鎖 → 輕量級(jí)鎖 → 重量級(jí)鎖)。
七、完整推導(dǎo)流程
- 問題背景:
- 多線程并發(fā)訪問共享資源可能導(dǎo)致數(shù)據(jù)不一致,需通過鎖保證線程安全。
- Monitor 是 synchronized 的核心實(shí)現(xiàn),管理互斥和線程協(xié)調(diào)。
- Monitor 的結(jié)構(gòu):
- 包含 Owner、EntryList、WaitSet、Recursions 等,記錄鎖狀態(tài)和線程隊(duì)列。
- 與對(duì)象頭的 Mark Word 交互,重量級(jí)鎖時(shí)存儲(chǔ) Monitor 指針。
- 工作流程:
- 獲取鎖:檢查重入、嘗試 CAS 獲取、或加入 EntryList 阻塞。
- 釋放鎖:減少重入計(jì)數(shù)、置空 Owner、喚醒等待線程。
- 等待/喚醒:通過 Wait Set 實(shí)現(xiàn) wait() 和 notify(),涉及鎖釋放和重新獲取。
- 底層實(shí)現(xiàn):
- ObjectMonitor 類管理 Monitor 邏輯,源碼在 objectMonitor.cpp。
- 依賴操作系統(tǒng) Mutex 和線程調(diào)度(park/unpark)。
- 優(yōu)化:
- 偏向鎖、輕量級(jí)鎖減少 Monitor 使用,重量級(jí)鎖作為 fallback。
- 自旋鎖和 CAS 提高效率。
八、通俗總結(jié)
- Monitor 是什么?它是 synchronized 的“門鎖”,確保線程排隊(duì)訪問共享資源。
- 怎么工作?像一個(gè)智能保安,記錄“誰在用”(Owner)、“誰在等”(EntryList/WaitSet),支持等待和喚醒。
- 為什么重要?沒有 Monitor,synchronized 無法實(shí)現(xiàn)線程安全和協(xié)調(diào)。
- 底層實(shí)現(xiàn)?通過 HotSpot JVM 的 ObjectMonitor 類,依賴操作系統(tǒng)互斥鎖和調(diào)度。
生活化比喻:
- Monitor 像一個(gè)廁所的智能門鎖:
- 有人用時(shí),標(biāo)上“占用”(Owner)。
- 有人排隊(duì)時(shí),記下名單(EntryList)。
- 有人出去喝咖啡時(shí),記在休息區(qū)(WaitSet)。
- 支持“熟客”反復(fù)進(jìn)出(重入),還能喊人回來(notify)。
九、源碼分析補(bǔ)充
以下是 ObjectMonitor 的關(guān)鍵方法(偽代碼簡化版),進(jìn)一步說明邏輯:
// 獲取鎖
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();
}
}關(guān)鍵點(diǎn):
- CAS:確保 _owner 設(shè)置的原子性。
- park/unpark:依賴 LockSupport,底層調(diào)用操作系統(tǒng)調(diào)度。
- 隊(duì)列管理:
ObjectWaiter節(jié)點(diǎn)維護(hù) EntryList 和 WaitSet。
十、擴(kuò)展閱讀
- 源碼推薦:
- HotSpot JVM:objectMonitor.hpp 和 objectMonitor.cpp(Monitor 實(shí)現(xiàn))。
- 相關(guān)文件:synchronizer.cpp(鎖狀態(tài)切換)、markOop.hpp(Mark Word)。
- 工具:
- 用 jstack 查看線程狀態(tài),分析 Monitor 競(jìng)爭。
- 用 JOL(Java Object Layout)查看對(duì)象頭和 Monitor 指針。
- 書籍:
- 《深入理解 Java 虛擬機(jī)》(周志明):講解 JVM 鎖和 Monitor 實(shí)現(xiàn)。
- 《Java 并發(fā)編程實(shí)戰(zhàn)》:結(jié)合 synchronized 理解 Monitor。
到此這篇關(guān)于Java 的 Monitor 機(jī)制之從原理與源碼解讀的文章就介紹到這了,更多相關(guān)java monitor原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 與 kotlin 使用Thymeleaf模板引擎渲染web視圖的方法
這篇文章主要介紹了Spring Boot 與 kotlin 使用Thymeleaf模板引擎渲染web視圖的方法,本文給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01
Java Hibernate中的持久化類和實(shí)體類關(guān)系
Hibernate是一種Java對(duì)象關(guān)系映射框架,通過持久化類將Java對(duì)象映射到數(shù)據(jù)庫表中。持久化類需要實(shí)現(xiàn)無參構(gòu)造器、具有標(biāo)識(shí)屬性和使用注解或XML進(jìn)行映射。Hibernate通過Session來管理對(duì)象的狀態(tài),包括臨時(shí)狀態(tài)、持久化狀態(tài)和游離狀態(tài)2023-04-04
spring中@ComponentScan自動(dòng)掃描并指定掃描規(guī)則
本文主要介紹了spring中@ComponentScan自動(dòng)掃描并指定掃描規(guī)則,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
SpringBoot項(xiàng)目發(fā)送釘釘消息功能實(shí)現(xiàn)
在工作中的一些告警需要發(fā)送釘釘通知,有的是發(fā)給個(gè)人,有的要發(fā)到群里,這時(shí)項(xiàng)目就需要接入釘釘,實(shí)現(xiàn)發(fā)消息的功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-02-02
myeclipse智能提示設(shè)置的實(shí)現(xiàn)方法
本篇文章介紹了,myeclipse智能提示設(shè)置的實(shí)現(xiàn)方法。需要的朋友參考下2013-05-05
關(guān)于break和continue以及l(fā)abel的區(qū)別和作用(詳解)
下面小編就為大家?guī)硪黄P(guān)于break和continue以及l(fā)abel的區(qū)別和作用(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05
SpringMVC源碼之HandlerMapping處理器映射器解析
這篇文章主要介紹了SpringMVC源碼之HandlerMapping處理器映射器解析,在Spring?MVC中,HandlerMapping處理器映射器用于確定請(qǐng)求處理器對(duì)象,請(qǐng)求處理器可以是任何對(duì)象,只要它們使用了@Controller注解或注解@RequestMapping,需要的朋友可以參考下2023-08-08
SpringBoot整合Servlet和Filter和Listener組件詳解
這篇文章主要介紹了SpringBoot整合Servlet和Filter和Listener組件詳解,在整合某報(bào)表插件時(shí)就需要使用Servlet,Spring Boot中對(duì)于整合這些基本的Web組件也提供了很好的支持,需要的朋友可以參考下2024-01-01

