Java synchronized底層的實現(xiàn)原理
前言:
想了解 synchronized 是如何運行的?就要先搞清楚 synchronized 是如何實現(xiàn)? synchronized 同步鎖是通過 JVM 內置的 Monitor 監(jiān)視器實現(xiàn)的,而監(jiān)視器又是依賴操作系統(tǒng)的互斥鎖 Mutex 實現(xiàn)的,那接下來我們先來了解一下監(jiān)視器。
監(jiān)視器
監(jiān)視器是一個概念或者說是一個機制,它用來保障在任何時候,只有一個線程能夠執(zhí)行指定區(qū)域的代碼。
一個監(jiān)視器像是一個建筑,建筑里有一個特殊的房間,這個房間同一時刻只能被一個線程所占有。一個線程從進入該房間到離開該房間,可以全程獨占該房間的所有數(shù)據(jù)。進入該建筑叫做進入監(jiān)視器(entering the monitor),進入該房間叫做獲得監(jiān)視器(acquiring the monitor),獨自占有該房間叫做擁有監(jiān)視器(owning the monitor),離開該房間叫做釋放監(jiān)視器(releasing the monitor),離開該建筑叫做退出監(jiān)視器(exiting the monitor)。
嚴格意義來說監(jiān)視器和鎖的概念是不同的,但很多地方也把二者相互指代。
底層實現(xiàn)
下面我們在代碼中添加一個 synchronized 代碼塊,來觀察一下它在字節(jié)碼層面是如何實現(xiàn)的?
示例代碼如下:
public class SynchronizedToMonitorExample {
public static void main(String[] args) {
int count = 0;
synchronized (SynchronizedToMonitorExample.class) {
for (int i = 0; i < 10; i++) {
count++;
}
}
System.out.println(count);
}
}當我們將上述代碼編譯成字節(jié)碼之后,得到的結果是這樣的:

從上述結果我們可以看出,在 main 方法中多了一對 monitorenter 和 monitorexit 的指令,它們的含義是:
- monitorenter:表示進入監(jiān)視器。
- monitorexit:表示退出監(jiān)視器。
由此可知 synchronized 是依賴 Monitor 監(jiān)視器實現(xiàn)的。
執(zhí)行流程
在 Java 中,synchronized 是非公平鎖,也是可以重入鎖。 所謂的非公平鎖是指,線程獲取鎖的順序不是按照訪問的順序先來先到的,而是由線程自己競爭,隨機獲取到鎖。 可重入鎖指的是,一個線程獲取到鎖之后,可以重復得到該鎖。這些內容是理解接下來內容的前置知識。 在 HotSpot 虛擬機中,Monitor 底層是由 C++實現(xiàn)的,它的實現(xiàn)對象是 ObjectMonitor,ObjectMonitor 結構體的實現(xiàn)如下:
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; //線程的重入次數(shù)
_object = NULL;
_owner = NULL; //標識擁有該monitor的線程
_WaitSet = NULL; //等待線程組成的雙向循環(huán)鏈表,_WaitSet是第一個節(jié)點
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多線程競爭鎖進入時的單向鏈表
FreeNext = NULL ;
_EntryList = NULL ; //_owner從該雙向循環(huán)鏈表中喚醒線程結點,_EntryList是第一個節(jié)點
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
} 在以上代碼中有幾個關鍵的屬性:
- _count:記錄該線程獲取鎖的次數(shù)(也就是前前后后,這個線程一共獲取此鎖多少次)。
- _recursions:鎖的重入次數(shù)。
- _owner:The Owner 擁有者,是持有該 ObjectMonitor(監(jiān)視器)對象的線程;
- _EntryList:EntryList 監(jiān)控集合,存放的是處于阻塞狀態(tài)的線程隊列,在多線程下,競爭失敗的線程會進入 EntryList 隊列。
- _WaitSet:WaitSet 待授權集合,存放的是處于 wait 狀態(tài)的線程隊列,當線程執(zhí)行了 wait() 方法之后,會進入 WaitSet 隊列。
監(jiān)視器執(zhí)行的流程如下:
- 線程通過 CAS(對比并替換)嘗試獲取鎖,如果獲取成功,就將 _owner 字段設置為當前線程,說明當前線程已經持有鎖,并將 _recursions 重入次數(shù)的屬性 +1。如果獲取失敗則先通過自旋 CAS 嘗試獲取鎖,如果還是失敗則將當前線程放入到 EntryList 監(jiān)控隊列(阻塞)。
- 當擁有鎖的線程執(zhí)行了 wait 方法之后,線程釋放鎖,將 owner 變量恢復為 null 狀態(tài),同時將該線程放入 WaitSet 待授權隊列中等待被喚醒。
- 當調用 notify 方法時,隨機喚醒 WaitSet 隊列中的某一個線程,當調用 notifyAll 時喚醒所有的 WaitSet 中的線程嘗試獲取鎖。
- 線程執(zhí)行完釋放了鎖之后,會喚醒 EntryList 中的所有線程嘗試獲取鎖。
以上就是監(jiān)視器的執(zhí)行流程,執(zhí)行流程如下圖所示:

總結
synchronized 同步鎖是通過 JVM 內置的 Monitor 監(jiān)視器實現(xiàn)的,而監(jiān)視器又是依賴操作系統(tǒng)的互斥鎖 Mutex 實現(xiàn)的。JVM 監(jiān)視器的執(zhí)行流程是:線程先通過自旋 CAS 的方式嘗試獲取鎖,如果獲取失敗就進入 EntrySet 集合,如果獲取成功就擁有該鎖。當調用 wait() 方法時,線程釋放鎖并進入 WaitSet 集合,等其他線程調用 notify 或 notifyAll 方法時再嘗試獲取鎖。鎖使用完之后就會通知 EntrySet 集合中的線程,讓它們嘗試獲取鎖。
到此這篇關于Java synchronized底層的實現(xiàn)原理的文章就介紹到這了,更多相關Java synchronized內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java類中生成jfreechart,返回圖表的url地址 代碼分享
這篇文章介紹了java類中生成jfreechart,返回圖表的url地址的代碼,有需要的朋友可以參考一下2013-08-08
Java整合RabbitMQ實現(xiàn)五種常見消費模型
本文將深入介紹RabbitMQ的五種常見消費模型,包括簡單隊列模型、工作隊列模型、發(fā)布/訂閱模型、路由模型和主題模型,刪除線格式并探討它們各自的優(yōu)缺點和適用場景,感興趣的可以了解一下2023-11-11
JavaSE實戰(zhàn)之酒店訂房系統(tǒng)的實現(xiàn)
這篇文章主要為大家詳細介紹了如何利用JavaSE實現(xiàn)酒店訂房系統(tǒng),文中的示例代碼講解詳細,對我們學習JavaSE開發(fā)有一定的幫助,需要的可以參考一下2022-07-07
智能 AI 代碼生成工具 Cursor 安裝和使用超詳細教程
Cursor.so 是一個集成了 GPT-4 的國內直接可以訪問的,優(yōu)秀而強大的免費代碼生成器,可以幫助你快速編寫、編輯和討論代碼,這篇文章主要介紹了智能 AI 代碼生成工具 Cursor 安裝和使用介紹,需要的朋友可以參考下2023-05-05

