1 參考資料
- Java併發編程的藝術
- 深入理解JVM
2 Volatile
2.1 Java工作內存模型
先上個圖,
正如圖所示,Java中線程是調度的基本單位,主存可以看成我們平常說的內存,線程的工作內存可以看成我們平時所說的緩存,爲了提高數據讀寫效率,我們常常會把經常訪問到的數據從內存中加載到緩存中(局部性原理)。
然後,由於Java中的工作內存是不共享的,所以,一個在主存中的共享變量,會在多個Java線程中有備份,當其中一個線程對該共享變量做了修改時。這就會出現點小問題是吧。
o( ̄︶ ̄)o 是的。
2.2 緩存一致性協議
既然出了問題,就得想辦法解決是吧。緩存一致性協議就是幹這個的。簡單地說,這個協議的作用就是,假設A B C 三個線程都有一個主存中共享變量D的備份,A修改了D後,馬上通知B C,你兩的緩存不起作用了,要再使用D的話,得去主存中再取一次。
2.3 Volatile-字節碼編譯
對帶有volatile修飾的變量進行寫操作時候,在彙編時會出現一個lock指令,
- 將當前Java工作內存中的修改後的變量更新到主存
- 使得其他Java工作內存中備份的變量無效
3 Synchronized
3.1 加鎖對象
- 普通方法–鎖住該方法屬於的實例對象
- 類方法(靜態方法)–鎖住該類的Class對象
- 代碼塊–鎖住Synchronized括號中的對象
3.2 底層實現
Synchronized字節碼上的實現是通過monitorenter和monitorexit來實現的。
- monitorenter指令在編譯後插入到同步代碼塊的起始位置,monitorexit則插入到方法結束處和異常處。
- monitorenter和monitorexit必須成對出現
- 任何一個對象都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。
3.3 鎖的種類和升級
Java 1.6之後,對Synchronized進行了優化,引入了偏向鎖和輕量級鎖。所以現在一般來說,鎖有四種狀態。
從加鎖的程度來看,可以分爲
無鎖狀態–> 偏向鎖 --> 輕量級鎖 --> 重量級鎖
emm,無鎖就不說了,下面簡單說下,偏向鎖,輕量級鎖和重量級鎖
3.3.1 偏向鎖
是啥? ---- “偏”可以理解爲偏心的意思,就是說,某個鎖的第一次(不要想歪!)被A線程獲取了,那麼在接下來的過程中且沒有其他線程獲取該鎖(沒有小三),持有該鎖的A線程則永遠不需要同步操作了。(真好)
怎麼實現?----我們知道,Java對象是有對象頭的,用來存儲hashcode,分代年齡,鎖信息等東西。上張圖
要實現偏向鎖的話,很簡單,在對象頭和棧幀的鎖記錄裏存儲鎖偏向的線程ID就可以了。
怎麼解除?----當有其他競爭者出現時,偏向鎖就不偏向了。
3.3.2 輕量級鎖及其膨脹(膨脹後就重量級了)
輕量級鎖和偏向鎖有點不同。
在執行同步塊之前,
- JVM會在當前線程棧幀中創建一個空間用於保存鎖記錄。
- 然後嘗試將對象的頭中的Mark Word複製到鎖記錄中。
- 通過CAS操作將對象頭中的Mark Word替換爲指向鎖記錄的指針。
如果成功,那就是獲得鎖了,沒有成功的話就自旋一會。