[Java]重學(xué)Java-synchronized

synchronized的作用

synchronized作為Java提供的鎖關(guān)鍵字,在單進(jìn)程的時候可以提供互斥的功能。
同時,由于其本身是一個關(guān)鍵字,它可以修飾方法對象、。

  • 當(dāng)synchronized修飾非static方法時,鎖住的是當(dāng)前的實(shí)例對象.
  • 當(dāng)synchronized修飾static方法時,鎖住的是當(dāng)前的Class對象.
  • 當(dāng)synchronized修飾一個類時,鎖住的是括號內(nèi)配置的Class對象.

關(guān)于這個關(guān)鍵字的原理,比較復(fù)雜,這里我查閱了《并發(fā)編程的藝術(shù)》這本十分經(jīng)典的書和一些其他的資料.
來跟各位淺析一波

synchronized的原理淺析

誰提供了鎖

這里其實(shí)是操作系統(tǒng)內(nèi)核提供了鎖的能力,sychronized是管程技術(shù)在Java領(lǐng)域的實(shí)現(xiàn),關(guān)于管程是什么,讀者可以通過以下鏈接去了解:

JVM中的monitorenter和monitorexit指令

首先,每個Java對象中都內(nèi)置了一個Monitor對象,他是用來控制當(dāng)前去搶占這個對象的鎖的線程的調(diào)度者,這里為了形象去描述,來看以下的圖:

monitor

假設(shè)我們把Condition這個類用synchronized修飾,那么它就會變成一個互斥訪問的模塊,這個由Condition所對應(yīng)的Monitor來保證.

我在知乎上摘抄了一段話,來讓你更好地理解Java中的Monitor對象:

zhihu

當(dāng)線程進(jìn)入和退出Monitor對象時,都通過monitorentermonitorexit指令,兩個指令必需成對出現(xiàn)。
在出現(xiàn)搶占現(xiàn)象時,Monitor處于鎖定狀態(tài),這里就要將無法獲得鎖資源的線程暫時阻塞,并放入等待隊(duì)列,當(dāng)占有鎖的線程解鎖后,從等待隊(duì)列中喚醒線程.

以下是比較好理解這個過程的一篇文章:
Java面試常見問題:Monitor對象是什么?

Mark Word

Java的Object對象包含了Object Header(對象頭)、數(shù)據(jù)部分、字節(jié)對齊部分(這是為了保證對象所占的內(nèi)存大小是8的倍數(shù)).

相關(guān)的知識可以去看看馬士兵老師在B站的一些免費(fèi)視頻,感興趣的朋友可以去看看.

Java虛擬機(jī)的對象頭包括了兩部分內(nèi)容:

  • Mark Word: 存儲對象自身的運(yùn)行時數(shù)據(jù),如hashCode,Generational Gc Age.
  • kclass: 存儲指向方法區(qū)對象類型數(shù)據(jù)的指針

在32位的Hotspot虛擬機(jī)中,無鎖的狀態(tài)下Mark Word的結(jié)構(gòu)是這樣分布的:

mark word

鎖升級

在synchronized的鎖升級過程中,鎖標(biāo)志位會進(jìn)行變更.

Mark Word存儲內(nèi)容 狀態(tài) 鎖標(biāo)志位
對象哈希碼+對象分代年齡 未鎖定 01
偏向線程ID,偏向時間戳、對象分代年齡 偏向鎖 01
指向鎖記錄的指針 輕量級鎖 00
指向重量級鎖的指針 重量級鎖(膨脹) 10

這里我們來總結(jié)一下規(guī)律:

  • 偏向鎖: 偏向第一個進(jìn)入當(dāng)前對象頭的線程,將對象頭的鎖標(biāo)志位設(shè)置為"01",同時用CAS操作把獲取到這個鎖的線程ID記錄在對象的MarkWord中,如果CAS成功,持有偏向鎖的線程進(jìn)入當(dāng)前鎖的同步塊時,虛擬機(jī)不需要進(jìn)行同步操作,直到另一個鎖來競爭當(dāng)前鎖時,偏向鎖結(jié)束.
    JVM默認(rèn)啟用,可以通過-XX:+UseBiasedLocking進(jìn)行配置.
偏向鎖
  • 輕量級鎖: 傳統(tǒng)的重量級鎖是依賴操作系統(tǒng)的內(nèi)核資源的,在少量線程競爭同步塊的時候,JVM并不會馬上采用重量級鎖來處理線程競爭,而是采用輕量級鎖,它的過程,本質(zhì)上是通過CAS來自旋搶奪Mark Word的過程,這里引用《并發(fā)編程的藝術(shù)》中的流程圖:
輕量級鎖

代碼進(jìn)入同步塊時,在線程對象中開辟一個Lock Record的空間,存儲當(dāng)前同步塊對象的Mark Word信息(Displaced Mark Word).
然后,虛擬機(jī)將使用CAS嘗試將同步塊對象的Mark Word更新為指向線程對象Lock Record的指針,一旦更新成功,那么這個線程就擁有了該對象的鎖,此時將Mark Word的鎖標(biāo)志位設(shè)置為"00".
如果更新失敗,虛擬機(jī)會檢查同步塊對象的Mark Word是否指向當(dāng)前線程對象的棧幀,如果是,則說明當(dāng)前對象已經(jīng)持有鎖了,則繼續(xù)進(jìn)入同步塊執(zhí)行代碼,否則說明存在線程競爭狀態(tài),此時會進(jìn)入自旋的過程,在JDK6前通過-XX:PreBlockSpin來設(shè)置自旋次數(shù),在JDK6后引入了自適應(yīng)自旋,旋轉(zhuǎn)次數(shù)根據(jù)前一次與同一個鎖上的自旋時間及鎖的擁有者狀態(tài)來決定,如果自旋獲取鎖失敗,則進(jìn)行鎖膨脹的過程。

  • 重量級鎖: 由objectMonitor實(shí)現(xiàn).

7000+字圖文并茂解帶你深入理解java鎖升級的每個細(xì)節(jié)

虛假喚醒

線程可以掛起狀態(tài)變成可運(yùn)行狀態(tài)(喚醒),即使該線程沒有被顯性調(diào)用notify()、notifyAll(),或者被中斷、待超時,這就是虛假喚醒.

解決這個問題的關(guān)鍵就是,通過循環(huán)去判斷當(dāng)前線程是否允許執(zhí)行:

sychronized(obj){
    while(條件不滿足){
        obj.wait();
    }
    // do something
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容