【Volatile和Synchronized 底層解析】

1 參考資料

  1. Java併發編程的藝術
  2. 深入理解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指令,
在這裏插入圖片描述

  1. 將當前Java工作內存中的修改後的變量更新到主存
  2. 使得其他Java工作內存中備份的變量無效

3 Synchronized

3.1 加鎖對象

  1. 普通方法–鎖住該方法屬於的實例對象
  2. 類方法(靜態方法)–鎖住該類的Class對象
  3. 代碼塊–鎖住Synchronized括號中的對象

3.2 底層實現

Synchronized字節碼上的實現是通過monitorenter和monitorexit來實現的。

  1. monitorenter指令在編譯後插入到同步代碼塊的起始位置,monitorexit則插入到方法結束處和異常處。
  2. monitorenter和monitorexit必須成對出現
  3. 任何一個對象都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。

3.3 鎖的種類和升級

Java 1.6之後,對Synchronized進行了優化,引入了偏向鎖和輕量級鎖。所以現在一般來說,鎖有四種狀態
從加鎖的程度來看,可以分爲

無鎖狀態–> 偏向鎖 --> 輕量級鎖 --> 重量級鎖

emm,無鎖就不說了,下面簡單說下,偏向鎖,輕量級鎖和重量級鎖

3.3.1 偏向鎖

是啥? ---- “偏”可以理解爲偏心的意思,就是說,某個鎖的第一次(不要想歪!)被A線程獲取了,那麼在接下來的過程中且沒有其他線程獲取該鎖(沒有小三),持有該鎖的A線程則永遠不需要同步操作了。(真好)

怎麼實現?----我們知道,Java對象是有對象頭的,用來存儲hashcode,分代年齡,鎖信息等東西。上張圖
在這裏插入圖片描述
要實現偏向鎖的話,很簡單,在對象頭和棧幀的鎖記錄裏存儲鎖偏向的線程ID就可以了。

怎麼解除?----當有其他競爭者出現時,偏向鎖就不偏向了。
在這裏插入圖片描述

3.3.2 輕量級鎖及其膨脹(膨脹後就重量級了)

輕量級鎖和偏向鎖有點不同。
在執行同步塊之前,

  1. JVM會在當前線程棧幀中創建一個空間用於保存鎖記錄。
  2. 然後嘗試將對象的頭中的Mark Word複製到鎖記錄中。
  3. 通過CAS操作將對象頭中的Mark Word替換爲指向鎖記錄的指針。

如果成功,那就是獲得鎖了,沒有成功的話就自旋一會。
在這裏插入圖片描述

3.4 鎖的優缺點對比

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章