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)度者,這里為了形象去描述,來看以下的圖:

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

當(dāng)線程進(jìn)入和退出Monitor對象時,都通過monitorenter和monitorexit指令,兩個指令必需成對出現(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)是這樣分布的:

鎖升級
在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
}