1. 程式人生 > 實用技巧 >各類鎖的對比及實現原理

各類鎖的對比及實現原理

2.1 什麼是鎖

Java中的每一個物件都可以作為鎖。具體表現為以下3種形式:

  • 對於普通同步方法,鎖是當前例項物件。
  • 對於靜態同步方法,鎖是當前類的 Class物件
  • 對於同步方法塊,鎖是 Synchonized括號裡配置的物件。

當一個執行緒試圖訪問同步程式碼塊時,它首先必須得到鎖,退出或丟擲異常時必須釋放鎖那麼鎖到底存在哪裡呢?鎖裡面會儲存什麼資訊呢?

JVM基於進入和退出 Monitor物件來實現方法同步程式碼塊同步,但兩者的實現細節不一樣。程式碼塊同步是使用 monitorentermonitorexit指令實現的,方法同步類似於程式碼塊同步。

monitorenter指令是在編譯後插入到同步程式碼塊

開始位置,而 monitorexit是插入到方法結束處異常處,JVM要保證每個 monitorenter必須有對應的 monitorexit-與之配對。任何物件都有一個 monitor與之關聯當一個物件的monitor被持有後,該物件將處於鎖定狀態。執行緒執行到 monitorenter指令時,將會嘗試獲取物件所對應的 monitor的所有權,即嘗試獲得物件的鎖。

2.2 Java物件頭

synchronized用的鎖是存在Java物件頭裡的。

如果物件是陣列型別,則虛擬機器用3個字寬(Word)儲存物件頭,如果物件是非陣列型別,則用2字寬儲存物件頭。在32位虛擬機器中,1字寬等於4位元組,即32bit,如表2-2所示。

Java物件頭裡的 Mark Word裡預設儲存物件的 HashCode、分代年齡和鎖標記位。32位JVM的 Mark Word的預設儲存結構,如下表所示:

Mark Word儲存結構

在執行期間,Mark Word裡儲存的資料會隨著鎖標誌位的變化而變化。Mark word可能變化為儲存以下4種資料,如表2-4所示:

在64位虛擬機器下,Mark Word是64bi大小的,其儲存結構如表2-5所示:

2.3 各類鎖的比對

為了減少獲得鎖和釋放鎖帶來的效能消耗,引入了偏向鎖輕量級鎖,在Java se1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態

,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖後不能降級成偏向鎖。

2.3.1 偏向鎖

什麼是偏向鎖

HotSpot的作者經過研究發現,大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同線程多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。

當一個執行緒訪問同步塊並獲取鎖時,會在物件頭棧幀中的鎖記錄裡儲存鎖偏向的執行緒ID,以後該執行緒在進入和退岀同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下物件頭的 Mark Word裡是否儲存著指向當前執行緒的偏向鎖

如果測試成功,表示執行緒已經獲得了鎖。

如果測試失敗,則需要再測試一下 Mark word中偏向鎖的標識是否設定成1(表示當前是偏向鎖)

​ 如果沒有設定,則使用CAS競爭鎖;

​ 如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。

偏向鎖的撤銷

當其他執行緒嘗試競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖。

偏向鎖的撤銷,需要等待全域性安全點(在這個時間點上沒有正在執行的位元組碼)。它會首先暫停擁有向鎖的執行緒,然後檢査持有偏向鎖的執行緒是否活著

如果執行緒不處於活動狀態,則將物件頭設定成無鎖狀態;

如果執行緒仍然活著,擁有偏向鎖的棧會被執行,它會遍歷偏向物件的鎖記錄,棧中的鎖記錄和物件頭的 Mark Word要麼重新偏向於其他執行緒,要麼恢復到無鎖或者標記物件不適合作為偏向鎖,最後喚醒暫停的執行緒。

圖2-1中的 執行緒1 演示了偏向鎖初始化的流程,執行緒2 演示了偏向鎖撤銷的流程。

關閉偏向鎖

偏向鎖在Java6和Java7裡是預設啟用的,但是它在應用程式後動幾秒鐘之後才啟用,如有必要可以使用JVM引數來關閉延遲:-XX:BiasedLockingStartup Delay=0。如果你確定應用程式裡所有的鎖通常情況下處於競爭狀態,可以通過JM引數關閉偏向鎖:-XX:Use Biasedlocking-false,那麼程式預設會進入輕量級鎖狀態。

2.3.2 輕量級鎖

輕量級鎖加鎖

執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並將物件頭中的 Mark word複製到鎖記錄中,官方稱為 Displaced Mark Word,然後執行緒嘗試使用CAS將物件頭中的 Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。

輕量級鎖解鎖

輕量級解鎖時,會使用原子的CAS操作將 Displaced Mark Word替換回到物件頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

圖2-2是兩個執行緒同時爭奪鎖,導致鎖膨脹的流程圖。

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的執行緒被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他執行緒試圖獲取鎖時,都會被阻塞住,當持有鎖的執行緒釋放鎖之後會喚醒這些執行緒,被喚醒的執行緒就會進行新一輪的奪鎖之爭。

2.3.3 鎖的對比