Java?synchronized底層實(shí)現(xiàn)原理以及鎖優(yōu)化
一、概述
synchronized簡介
在多線程并發(fā)編程中 synchronized 一直是元老級(jí)角色,很多人都會(huì)稱呼它為重量級(jí)鎖。但是,隨著 Java SE 1.6 對(duì)synchronized 進(jìn)行了各種優(yōu)化之后,有些情況下它就并不那么重,Java SE 1.6 中為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級(jí)鎖。這塊在后續(xù)介紹中會(huì)慢慢引入。
synchronized的基本語法
synchronized 有三種方式來加鎖,分別是
- 修飾實(shí)例方法,作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖
- 靜態(tài)方法,作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖
- 修飾代碼塊,指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼庫前要獲得給定對(duì)象的鎖。
synchronized作用
- 原子性:synchronized保證語句塊內(nèi)操作是原子的
- 可見性:synchronized保證可見性(通過“在執(zhí)行unlock之前,必須先把此變量同步回主內(nèi)存”實(shí)現(xiàn))
- 有序性:synchronized保證有序性(通過“一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”)
synchronized的使用
- 修飾實(shí)例方法,對(duì)當(dāng)前實(shí)例對(duì)象加鎖
- 修飾靜態(tài)方法,多當(dāng)前類的Class對(duì)象加鎖
- 修飾代碼塊,對(duì)synchronized括號(hào)內(nèi)的對(duì)象加鎖
二、實(shí)現(xiàn)原理
1、jvm基于進(jìn)入和退出Monitor對(duì)象來實(shí)現(xiàn)方法同步和代碼塊同步。
方法級(jí)的同步是隱式,即無需通過字節(jié)碼指令來控制的,它實(shí)現(xiàn)在方法調(diào)用和返回操作之中。JVM可以從方法常量池中的方法表結(jié)構(gòu)(method_info Structure) 中的 ACC_SYNCHRONIZED 訪問標(biāo)志區(qū)分一個(gè)方法是否同步方法。當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì) 檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先持有monitor(虛擬機(jī)規(guī)范中用的是管程一詞), 然后再執(zhí)行方法,最后再方法完成(無論是正常完成還是非正常完成)時(shí)釋放monitor。
代碼塊的同步是利用monitorenter和monitorexit這兩個(gè)字節(jié)碼指令。它們分別位于同步代碼塊的開始和結(jié)束位置。當(dāng)jvm執(zhí)行到monitorenter指令時(shí),當(dāng)前線程試圖獲取monitor對(duì)象的所有權(quán),如果未加鎖或者已經(jīng)被當(dāng)前線程所持有,就把鎖的計(jì)數(shù)器+1;當(dāng)執(zhí)行monitorexit指令時(shí),鎖計(jì)數(shù)器-1;當(dāng)鎖計(jì)數(shù)器為0時(shí),該鎖就被釋放了。如果獲取monitor對(duì)象失敗,該線程則會(huì)進(jìn)入阻塞狀態(tài),直到其他線程釋放鎖。
這里要注意:
- synchronized是可重入的,所以不會(huì)自己把,自己鎖死
- synchronized鎖一旦被一個(gè)線程持有,其他試圖獲取該鎖的線程將被阻塞。
關(guān)于ACC_SYNCHRONIZED 、monitorenter、monitorexit指令,可以看一下下面的反編譯代碼:
public class SynchronizedDemo { public synchronized void f(){ //這個(gè)是同步方法 System.out.println("Hello world"); } public void g(){ synchronized (this){ //這個(gè)是同步代碼塊 System.out.println("Hello world"); } } public static void main(String[] args) { } }
使用javap -verbose SynchronizedDemo
反編譯后得到
我們看到對(duì)于同步方法,反編譯后得到ACC_SYNCHRONIZED 標(biāo)志,對(duì)于同步代碼塊反編譯后得到monitorenter和monitorexit指令。
三、理解Java對(duì)象頭
在JVM中,對(duì)象在內(nèi)存中的布局分為三塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。
實(shí)例變量:存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息,如果是數(shù)組的實(shí)例部分還包括數(shù)組的長度,這部分內(nèi)存按4字節(jié)對(duì)齊。
填充數(shù)據(jù):由于虛擬機(jī)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍。填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對(duì)齊。
HotSpot虛擬機(jī)的對(duì)象頭分為兩部分信息,第一部分用于存儲(chǔ)對(duì)象自身運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC分代年齡等,這部分?jǐn)?shù)據(jù)的長度在32位和64位的虛擬機(jī)中分別為32位和64位。官方稱為Mark Word。另一部分用于存儲(chǔ)指向?qū)ο箢愋蛿?shù)據(jù)的指針,如果是數(shù)組對(duì)象的話,還會(huì)有一個(gè)額外的部分存儲(chǔ)數(shù)組長度。
虛擬機(jī)位數(shù) | 對(duì)象頭結(jié)構(gòu) | 描述 |
---|---|---|
32位/64位 | Mark Word | 存儲(chǔ)對(duì)象的哈希碼、GC分代年齡、鎖信息等 |
32位/64位 | Class MetaData Address | 指向?qū)ο箢愋蛿?shù)據(jù)的指針 |
32位/64位 | 數(shù)組長度 | 如果是數(shù)組對(duì)象的話,有這一部分,否則沒有 |
由于對(duì)象頭的信息是與對(duì)象自身定義的數(shù)據(jù)沒有關(guān)系的額外存儲(chǔ)成本,因此考慮到JVM的空間效率,Mark Word 被設(shè)計(jì)成為一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu),以便存儲(chǔ)更多有效的數(shù)據(jù),它會(huì)根據(jù)對(duì)象本身的狀態(tài)復(fù)用自己的存儲(chǔ)空間。
四、JVM對(duì)synchronized的鎖優(yōu)化
Synchronized是通過對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來實(shí)現(xiàn)的,監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的Mutex Lock(互斥鎖)來實(shí)現(xiàn)的。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長的時(shí)間,這就是為什么Synchronized效率低的原因。因此,這種依賴于操作系統(tǒng)Mutex Lock所實(shí)現(xiàn)的鎖我們稱之為“重量級(jí)鎖”。
Java SE 1.6為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”:鎖一共有4種狀態(tài),級(jí)別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài)。鎖可以升級(jí)但不能降級(jí)。
1、偏向鎖
偏向鎖是JDK1.6中引用的優(yōu)化,它的目的是消除數(shù)據(jù)在無競爭情況下的同步原語,進(jìn)一步提高程序的性能。
偏向鎖的獲?。?/p>
- 判斷是否為可偏向狀態(tài)
- 如果為可偏向狀態(tài),則判斷線程ID是否是當(dāng)前線程,如果是進(jìn)入同步塊;
- 如果線程ID并未指向當(dāng)前線程,利用CAS操作競爭鎖,如果競爭成功,將Mark Word中線程ID更新為當(dāng)前線程ID,進(jìn)入同步塊
- 如果競爭失敗,等待全局安全點(diǎn),準(zhǔn)備撤銷偏向鎖,根據(jù)線程是否處于活動(dòng)狀態(tài),決定是轉(zhuǎn)換為無鎖狀態(tài)還是升級(jí)為輕量級(jí)鎖。
當(dāng)鎖對(duì)象第一次被線程獲取的時(shí)候,虛擬機(jī)會(huì)把對(duì)象頭中的標(biāo)志位設(shè)置為“01”,即偏向模式。同時(shí)使用CAS操作把獲取到這個(gè)鎖的線程ID記錄在對(duì)象的Mark Word中,如果CAS操作成功。持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí),虛擬機(jī)都可以不再進(jìn)行任何同步操作。
偏向鎖的釋放:
偏向鎖使用了遇到競爭才釋放鎖的機(jī)制。偏向鎖的撤銷需要等待全局安全點(diǎn),然后它會(huì)首先暫停擁有偏向鎖的線程,然后判斷線程是否還活著,如果線程還活著,則升級(jí)為輕量級(jí)鎖,否則,將鎖設(shè)置為無鎖狀態(tài)。
2、輕量級(jí)鎖
輕量級(jí)鎖也是在JDK1.6中引入的新型鎖機(jī)制。它不是用來替換重量級(jí)鎖的,它的本意是在沒有多線程競爭的情況下,減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗。
加鎖過程:
在代碼進(jìn)入同步塊的時(shí)候,如果此對(duì)象沒有被鎖定(鎖標(biāo)志位為“01”狀態(tài)),虛擬機(jī)首先在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)對(duì)象目前Mark Word的拷貝(官方把這份拷貝加了一個(gè)Displaced前綴,即Displaced Mark Word)。然后虛擬機(jī)使用CAS操作嘗試將對(duì)象的Mark Word更新為指向鎖記錄(Lock Record)的指針。如果更新成功,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象的Mark Word標(biāo)志位轉(zhuǎn)變?yōu)?ldquo;00”,即表示此對(duì)象處于輕量級(jí)鎖定狀態(tài);如果更新失敗,虛擬機(jī)首先會(huì)檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果說明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那就可以直接進(jìn)入同步塊中執(zhí)行,否則說明這個(gè)鎖對(duì)象已經(jīng)被其他線程占有了。如果有兩條以上的線程競爭同一個(gè)鎖,那輕量級(jí)鎖不再有效,要膨脹為重量級(jí)鎖,鎖標(biāo)志變?yōu)?ldquo;10”,Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖的指針,而后面等待的線程也要進(jìn)入阻塞狀態(tài)。
解鎖過程:
如果對(duì)象的Mark Word仍然指向線程的鎖記錄,那就用CAS操作將對(duì)象當(dāng)前的Mark Word與線程棧幀中的Displaced Mark Word交換回來,如果替換成功,整個(gè)同步過程就完成了。如果替換失敗,說明有其他線程嘗試過獲取該鎖,那就要在釋放鎖的同時(shí),喚醒被掛起的線程。
如果沒有競爭,輕量級(jí)鎖使用CAS操作避免了使用互斥量的開銷,但如果存在競爭,除了互斥量的開銷外,還額外發(fā)生了CAS操作,因此在有競爭的情況下,輕量級(jí)鎖比傳統(tǒng)重量級(jí)鎖開銷更大。
3、重量級(jí)鎖
Synchronized的重量級(jí)鎖是通過對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來實(shí)現(xiàn)的,監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的Mutex Lock(互斥鎖)來實(shí)現(xiàn)的。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長的時(shí)間,這就是為什么Synchronized效率低的原因。
4、自旋鎖
互斥同步對(duì)性能影響最大的是阻塞的實(shí)現(xiàn),掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入到內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性能帶來很大的壓力。
于是在阻塞之前,我們讓線程執(zhí)行一個(gè)忙循環(huán)(自旋),看看持有鎖的線程是否釋放鎖,如果很快釋放鎖,則沒有必要進(jìn)行阻塞。
5、鎖消除
鎖消除是指虛擬機(jī)即時(shí)編譯器(JIT)在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是檢測(cè)到不可能發(fā)生數(shù)據(jù)競爭的鎖進(jìn)行消除。
6、鎖粗化
如果虛擬機(jī)檢測(cè)到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部。
參考:深入理解Java并發(fā)之synchronized實(shí)現(xiàn)原理
參考:Java的對(duì)象頭和對(duì)象組成詳解
總結(jié)
到此這篇關(guān)于Java synchronized底層實(shí)現(xiàn)原理以及鎖優(yōu)化的文章就介紹到這了,更多相關(guān)synchronized底層實(shí)現(xiàn)原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中進(jìn)制的轉(zhuǎn)換,Byte與16進(jìn)制的轉(zhuǎn)換方法
下面小編就為大家?guī)硪黄猨ava中進(jìn)制的轉(zhuǎn)換,Byte與16進(jìn)制的轉(zhuǎn)換方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11兩種Eclipse部署動(dòng)態(tài)web項(xiàng)目方法
這篇文章主要介紹了兩種Eclipse部署動(dòng)態(tài)web項(xiàng)目方法,需要的朋友可以參考下2015-11-11Spring Boot應(yīng)用的極速部署腳本示例代碼
最近在工作中遇到了一個(gè)問題,需要極速的部署Spring Boot應(yīng)用,發(fā)現(xiàn)網(wǎng)上這方面的資料較少,所以自己來總結(jié)下,這篇文章主要給大家介紹了關(guān)于Spring Boot應(yīng)用的極速部署腳本的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08Java微信公眾平臺(tái)開發(fā)(3) 接收消息的分類及實(shí)體的創(chuàng)建
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺(tái)開發(fā)第三步,接收消息的分類及實(shí)體的創(chuàng)建,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04jasypt SaltGenerator接口定義方法源碼解讀
這篇文章主要為大家介紹了jasypt SaltGenerator接口定義方法源碼解讀,,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09String字符串拼接方法concat和+的效率對(duì)比
這篇文章主要介紹了String字符串拼接方法concat和+的效率對(duì)比,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Springboot詳解實(shí)現(xiàn)食品倉庫管理系統(tǒng)流程
這是一個(gè)使用Springboot開發(fā)的食品倉庫管理系統(tǒng),是為商家提供商品貨物進(jìn)銷存的信息化管理系統(tǒng),具有一個(gè)倉庫管理系統(tǒng)該有的所有功能,感興趣的朋友快來看看吧2022-06-06使用java + selenium + OpenCV破解騰訊防水墻滑動(dòng)驗(yàn)證碼功能
這篇文章主要介紹了使用java + selenium + OpenCV破解騰訊防水墻滑動(dòng)驗(yàn)證碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11