synchronized的作用
synchronized作為Java提供的鎖關(guān)鍵字,在單進(jìn)程的時(shí)候可以提供互斥的功能。
同時(shí),由于其本身是一個(gè)關(guān)鍵字,它可以修飾方法、對(duì)象、類。
- 當(dāng)
synchronized修飾非static方法時(shí),鎖住的是當(dāng)前的實(shí)例對(duì)象. - 當(dāng)
synchronized修飾static方法時(shí),鎖住的是當(dāng)前的Class對(duì)象. - 當(dāng)
synchronized修飾一個(gè)類時(shí),鎖住的是括號(hào)內(nèi)配置的Class對(duì)象.
關(guān)于這個(gè)關(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指令
首先,每個(gè)Java對(duì)象中都內(nèi)置了一個(gè)Monitor對(duì)象,他是用來控制當(dāng)前去搶占這個(gè)對(duì)象的鎖的線程的調(diào)度者,這里為了形象去描述,來看以下的圖:

假設(shè)我們把Condition這個(gè)類用synchronized修飾,那么它就會(huì)變成一個(gè)互斥訪問的模塊,這個(gè)由Condition所對(duì)應(yīng)的Monitor來保證.
我在知乎上摘抄了一段話,來讓你更好地理解Java中的Monitor對(duì)象:

當(dāng)線程進(jìn)入和退出Monitor對(duì)象時(shí),都通過monitorenter和monitorexit指令,兩個(gè)指令必需成對(duì)出現(xiàn)。
在出現(xiàn)搶占現(xiàn)象時(shí),Monitor處于鎖定狀態(tài),這里就要將無法獲得鎖資源的線程暫時(shí)阻塞,并放入等待隊(duì)列,當(dāng)占有鎖的線程解鎖后,從等待隊(duì)列中喚醒線程.
以下是比較好理解這個(gè)過程的一篇文章:
Java面試常見問題:Monitor對(duì)象是什么?
Mark Word
Java的Object對(duì)象包含了Object Header(對(duì)象頭)、數(shù)據(jù)部分、字節(jié)對(duì)齊部分(這是為了保證對(duì)象所占的內(nèi)存大小是8的倍數(shù)).
相關(guān)的知識(shí)可以去看看馬士兵老師在B站的一些免費(fèi)視頻,感興趣的朋友可以去看看.
Java虛擬機(jī)的對(duì)象頭包括了兩部分內(nèi)容:
- Mark Word: 存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如
hashCode,Generational Gc Age. - kclass: 存儲(chǔ)指向方法區(qū)對(duì)象類型數(shù)據(jù)的指針
在32位的Hotspot虛擬機(jī)中,無鎖的狀態(tài)下Mark Word的結(jié)構(gòu)是這樣分布的:

鎖升級(jí)
在synchronized的鎖升級(jí)過程中,鎖標(biāo)志位會(huì)進(jìn)行變更.
| Mark Word存儲(chǔ)內(nèi)容 | 狀態(tài) | 鎖標(biāo)志位 |
|---|---|---|
| 對(duì)象哈希碼+對(duì)象分代年齡 | 未鎖定 | 01 |
| 偏向線程ID,偏向時(shí)間戳、對(duì)象分代年齡 | 偏向鎖 | 01 |
| 指向鎖記錄的指針 | 輕量級(jí)鎖 | 00 |
| 指向重量級(jí)鎖的指針 | 重量級(jí)鎖(膨脹) | 10 |
這里我們來總結(jié)一下規(guī)律:
-
偏向鎖: 偏向第一個(gè)進(jìn)入當(dāng)前對(duì)象頭的線程,將對(duì)象頭的鎖標(biāo)志位設(shè)置為"01",同時(shí)用CAS操作把獲取到這個(gè)鎖的線程ID記錄在對(duì)象的MarkWord中,如果CAS成功,持有偏向鎖的線程進(jìn)入當(dāng)前鎖的同步塊時(shí),虛擬機(jī)不需要進(jìn)行同步操作,直到另一個(gè)鎖來競(jìng)爭(zhēng)當(dāng)前鎖時(shí),偏向鎖結(jié)束.
JVM默認(rèn)啟用,可以通過-XX:+UseBiasedLocking進(jìn)行配置.

- 輕量級(jí)鎖: 傳統(tǒng)的重量級(jí)鎖是依賴操作系統(tǒng)的內(nèi)核資源的,在少量線程競(jìng)爭(zhēng)同步塊的時(shí)候,JVM并不會(huì)馬上采用重量級(jí)鎖來處理線程競(jìng)爭(zhēng),而是采用輕量級(jí)鎖,它的過程,本質(zhì)上是通過CAS來自旋搶奪Mark Word的過程,這里引用《并發(fā)編程的藝術(shù)》中的流程圖:

代碼進(jìn)入同步塊時(shí),在線程對(duì)象中開辟一個(gè)Lock Record的空間,存儲(chǔ)當(dāng)前同步塊對(duì)象的Mark Word信息(Displaced Mark Word).
然后,虛擬機(jī)將使用CAS嘗試將同步塊對(duì)象的Mark Word更新為指向線程對(duì)象Lock Record的指針,一旦更新成功,那么這個(gè)線程就擁有了該對(duì)象的鎖,此時(shí)將Mark Word的鎖標(biāo)志位設(shè)置為"00".
如果更新失敗,虛擬機(jī)會(huì)檢查同步塊對(duì)象的Mark Word是否指向當(dāng)前線程對(duì)象的棧幀,如果是,則說明當(dāng)前對(duì)象已經(jīng)持有鎖了,則繼續(xù)進(jìn)入同步塊執(zhí)行代碼,否則說明存在線程競(jìng)爭(zhēng)狀態(tài),此時(shí)會(huì)進(jìn)入自旋的過程,在JDK6前通過-XX:PreBlockSpin來設(shè)置自旋次數(shù),在JDK6后引入了自適應(yīng)自旋,旋轉(zhuǎn)次數(shù)根據(jù)前一次與同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者狀態(tài)來決定,如果自旋獲取鎖失敗,則進(jìn)行鎖膨脹的過程。
- 重量級(jí)鎖: 由objectMonitor實(shí)現(xiàn).
7000+字圖文并茂解帶你深入理解java鎖升級(jí)的每個(gè)細(xì)節(jié)
虛假喚醒
線程可以掛起狀態(tài)變成可運(yùn)行狀態(tài)(喚醒),即使該線程沒有被顯性調(diào)用notify()、notifyAll(),或者被中斷、待超時(shí),這就是虛假喚醒.
解決這個(gè)問題的關(guān)鍵就是,通過循環(huán)去判斷當(dāng)前線程是否允許執(zhí)行:
sychronized(obj){
while(條件不滿足){
obj.wait();
}
// do something
}