Java synchronized底層的實(shí)現(xiàn)原理
前言:
想了解 synchronized 是如何運(yùn)行的?就要先搞清楚 synchronized 是如何實(shí)現(xiàn)? synchronized 同步鎖是通過 JVM 內(nèi)置的 Monitor 監(jiān)視器實(shí)現(xiàn)的,而監(jiān)視器又是依賴操作系統(tǒng)的互斥鎖 Mutex 實(shí)現(xiàn)的,那接下來我們先來了解一下監(jiān)視器。
監(jiān)視器
監(jiān)視器是一個(gè)概念或者說是一個(gè)機(jī)制,它用來保障在任何時(shí)候,只有一個(gè)線程能夠執(zhí)行指定區(qū)域的代碼。
一個(gè)監(jiān)視器像是一個(gè)建筑,建筑里有一個(gè)特殊的房間,這個(gè)房間同一時(shí)刻只能被一個(gè)線程所占有。一個(gè)線程從進(jìn)入該房間到離開該房間,可以全程獨(dú)占該房間的所有數(shù)據(jù)。進(jìn)入該建筑叫做進(jìn)入監(jiān)視器(entering the monitor),進(jìn)入該房間叫做獲得監(jiān)視器(acquiring the monitor),獨(dú)自占有該房間叫做擁有監(jiān)視器(owning the monitor),離開該房間叫做釋放監(jiān)視器(releasing the monitor),離開該建筑叫做退出監(jiān)視器(exiting the monitor)。
嚴(yán)格意義來說監(jiān)視器和鎖的概念是不同的,但很多地方也把二者相互指代。
底層實(shí)現(xiàn)
下面我們在代碼中添加一個(gè) synchronized 代碼塊,來觀察一下它在字節(jié)碼層面是如何實(shí)現(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); } }
當(dāng)我們將上述代碼編譯成字節(jié)碼之后,得到的結(jié)果是這樣的:
從上述結(jié)果我們可以看出,在 main 方法中多了一對 monitorenter 和 monitorexit 的指令,它們的含義是:
- monitorenter:表示進(jìn)入監(jiān)視器。
- monitorexit:表示退出監(jiān)視器。
由此可知 synchronized 是依賴 Monitor 監(jiān)視器實(shí)現(xiàn)的。
執(zhí)行流程
在 Java 中,synchronized 是非公平鎖,也是可以重入鎖。 所謂的非公平鎖是指,線程獲取鎖的順序不是按照訪問的順序先來先到的,而是由線程自己競爭,隨機(jī)獲取到鎖。 可重入鎖指的是,一個(gè)線程獲取到鎖之后,可以重復(fù)得到該鎖。這些內(nèi)容是理解接下來內(nèi)容的前置知識。 在 HotSpot 虛擬機(jī)中,Monitor 底層是由 C++實(shí)現(xiàn)的,它的實(shí)現(xiàn)對象是 ObjectMonitor,ObjectMonitor 結(jié)構(gòu)體的實(shí)現(xiàn)如下:
ObjectMonitor::ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; //線程的重入次數(shù) _object = NULL; _owner = NULL; //標(biāo)識擁有該monitor的線程 _WaitSet = NULL; //等待線程組成的雙向循環(huán)鏈表,_WaitSet是第一個(gè)節(jié)點(diǎn) _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; //多線程競爭鎖進(jìn)入時(shí)的單向鏈表 FreeNext = NULL ; _EntryList = NULL ; //_owner從該雙向循環(huán)鏈表中喚醒線程結(jié)點(diǎn),_EntryList是第一個(gè)節(jié)點(diǎn) _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
在以上代碼中有幾個(gè)關(guān)鍵的屬性:
- _count:記錄該線程獲取鎖的次數(shù)(也就是前前后后,這個(gè)線程一共獲取此鎖多少次)。
- _recursions:鎖的重入次數(shù)。
- _owner:The Owner 擁有者,是持有該 ObjectMonitor(監(jiān)視器)對象的線程;
- _EntryList:EntryList 監(jiān)控集合,存放的是處于阻塞狀態(tài)的線程隊(duì)列,在多線程下,競爭失敗的線程會(huì)進(jìn)入 EntryList 隊(duì)列。
- _WaitSet:WaitSet 待授權(quán)集合,存放的是處于 wait 狀態(tài)的線程隊(duì)列,當(dāng)線程執(zhí)行了 wait() 方法之后,會(huì)進(jìn)入 WaitSet 隊(duì)列。
監(jiān)視器執(zhí)行的流程如下:
- 線程通過 CAS(對比并替換)嘗試獲取鎖,如果獲取成功,就將 _owner 字段設(shè)置為當(dāng)前線程,說明當(dāng)前線程已經(jīng)持有鎖,并將 _recursions 重入次數(shù)的屬性 +1。如果獲取失敗則先通過自旋 CAS 嘗試獲取鎖,如果還是失敗則將當(dāng)前線程放入到 EntryList 監(jiān)控隊(duì)列(阻塞)。
- 當(dāng)擁有鎖的線程執(zhí)行了 wait 方法之后,線程釋放鎖,將 owner 變量恢復(fù)為 null 狀態(tài),同時(shí)將該線程放入 WaitSet 待授權(quán)隊(duì)列中等待被喚醒。
- 當(dāng)調(diào)用 notify 方法時(shí),隨機(jī)喚醒 WaitSet 隊(duì)列中的某一個(gè)線程,當(dāng)調(diào)用 notifyAll 時(shí)喚醒所有的 WaitSet 中的線程嘗試獲取鎖。
- 線程執(zhí)行完釋放了鎖之后,會(huì)喚醒 EntryList 中的所有線程嘗試獲取鎖。
以上就是監(jiān)視器的執(zhí)行流程,執(zhí)行流程如下圖所示:
總結(jié)
synchronized 同步鎖是通過 JVM 內(nèi)置的 Monitor 監(jiān)視器實(shí)現(xiàn)的,而監(jiān)視器又是依賴操作系統(tǒng)的互斥鎖 Mutex 實(shí)現(xiàn)的。JVM 監(jiān)視器的執(zhí)行流程是:線程先通過自旋 CAS 的方式嘗試獲取鎖,如果獲取失敗就進(jìn)入 EntrySet 集合,如果獲取成功就擁有該鎖。當(dāng)調(diào)用 wait() 方法時(shí),線程釋放鎖并進(jìn)入 WaitSet 集合,等其他線程調(diào)用 notify 或 notifyAll 方法時(shí)再嘗試獲取鎖。鎖使用完之后就會(huì)通知 EntrySet 集合中的線程,讓它們嘗試獲取鎖。
到此這篇關(guān)于Java synchronized底層的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Java synchronized內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java必會(huì)的Synchronized底層原理剖析
- Java同步鎖Synchronized底層源碼和原理剖析(推薦)
- java并發(fā)編程synchronized底層實(shí)現(xiàn)原理
- Java?synchronized底層實(shí)現(xiàn)原理以及鎖優(yōu)化
- Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 下
- Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 上
- Java 并發(fā)編程學(xué)習(xí)筆記之Synchronized底層優(yōu)化
- Java Synchronized字節(jié)碼層分析體驗(yàn)
相關(guān)文章
springboot如何統(tǒng)一設(shè)置時(shí)區(qū)
這篇文章主要介紹了springboot如何統(tǒng)一設(shè)置時(shí)區(qū)問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01springboot整合Nginx實(shí)現(xiàn)負(fù)載均衡反向代理的方法詳解
這篇文章主要給大家介紹了關(guān)于springboot整合Nginx實(shí)現(xiàn)負(fù)載均衡反向代理的相關(guān)資料,文中通過圖文以及實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01Java如何設(shè)置系統(tǒng)參數(shù)和運(yùn)行參數(shù)
這篇文章主要介紹了Java如何設(shè)置系統(tǒng)參數(shù)和運(yùn)行參數(shù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04基于FlashPaper實(shí)現(xiàn)JSP在線閱讀代碼示例
這篇文章主要介紹了基于FlashPaper實(shí)現(xiàn)JSP在線閱讀代碼示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)
這篇文章主要介紹了java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)包括了:配置線程池大小配置,線程池的實(shí)現(xiàn)原理等,需要的朋友可以參考下2023-02-02云計(jì)算實(shí)驗(yàn):Java?MapReduce編程
這篇文章主要介紹了云計(jì)算實(shí)驗(yàn):Java?MapReduce編程,?居于Java圍繞MapReduce編程展開詳細(xì)內(nèi)容,文章助大家掌握MapReduce編程,理解MapReduce原理,需要的朋友可以參考一下2021-12-12Java實(shí)現(xiàn)lucene搜索功能的方法(推薦)
下面小編就為大家?guī)硪黄狫ava實(shí)現(xiàn)lucene搜索功能的方法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12