1. 程式人生 > >偏向鎖、輕量級鎖和重量級鎖

偏向鎖、輕量級鎖和重量級鎖

Synchronized的偏向鎖、輕量級鎖以及重量級鎖是通過Java物件頭實現的。Java物件的記憶體佈局分為:物件頭、例項資料和對其填充,而物件頭又可以分為”Mark Word”和型別指標klass微笑。”Mark Word”是關鍵,預設情況下,其儲存物件的HashCode、分代年齡和鎖標記位。

這裡說的都是以HotSpot虛擬機器為基準的。首先來看一下”Mark Word”的內容:


注意到這裡的無鎖和偏向鎖在”Mark Word”的倒數第三bit中分別採用0和1標記。
偏向鎖是JDK6中引入的一項鎖優化,它的目的是消除資料在無競爭情況下的同步原語,進一步提高程式的執行效能。

偏向鎖會偏向於第一個獲得它的執行緒,如果在接下來的執行過程中,該鎖沒有被其他的執行緒獲取,則持有偏向鎖的執行緒將永遠不需要同步。大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。

當鎖物件第一次被執行緒獲取的時候,執行緒使用CAS操作把這個鎖的執行緒ID記錄再物件Mark Word之中,同時置偏向標誌位1。以後該執行緒在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需要簡單地測試一下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。

如果執行緒使用CAS操作時失敗則表示該鎖物件上存在競爭並且這個時候另外一個執行緒獲得偏向鎖的所有權。當到達全域性安全點(safepoint,這個時間點上沒有正在執行的位元組碼)時獲得偏向鎖的執行緒被掛起,膨脹為輕量級鎖(涉及Monitor Record,Lock Record相關操作,這裡不展開),同時被撤銷偏向鎖的執行緒繼續往下執行同步程式碼。

當有另外一個執行緒去嘗試獲取這個鎖時,偏向模式就宣告結束。

執行緒在執行同步塊之前,JVM會先在當前執行緒的棧幀中建立用於儲存鎖記錄(Lock Record)的空間,並將物件頭中的Mard Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後執行緒嘗試使用CAS將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。如果自旋失敗則鎖會膨脹成重量級鎖。如果自旋成功則依然處於輕量級鎖的狀態。

輕量級鎖的解鎖過程也是通過CAS操作來進行的,如果物件的Mark Word仍然指向執行緒的鎖記錄,那就用CAS操作把物件當前的Mark Word和執行緒中賦值的Displaced Mark Word替換回來,如果替換成功,整個同步過程就完成了,如果替換失敗,就說明有其他執行緒嘗試過獲取該鎖,那就要在釋放鎖的同時,喚醒被掛起的執行緒。

輕量級鎖提升程式同步效能的依據是:對於絕大部分的鎖,在整個同步週期內都是不存在競爭的(區別於偏向鎖)。這是一個經驗資料。如果沒有競爭,輕量級鎖使用CAS操作避免了使用互斥量的開銷,但如果存在鎖競爭,除了互斥量的開銷外,還額外發生了CAS操作,因此在有競爭的情況下,輕量級鎖比傳統的重量級鎖更慢。

整個Synchronized鎖流程如下:


  1. 檢測Mark Word裡面是不是當前執行緒的ID,如果是,表示當前執行緒處於偏向鎖
  2. 如果不是,則使用CAS將當前執行緒的ID替換Mard Word,如果成功則表示當前執行緒獲得偏向鎖,置偏向標誌位1
  3. 如果失敗,則說明發生競爭,撤銷偏向鎖,進而升級為輕量級鎖。
  4. 當前執行緒使用CAS將物件頭的Mark Word替換為鎖記錄指標,如果成功,當前執行緒獲得鎖
  5. 如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。
  6. 如果自旋成功則依然處於輕量級狀態。
  7. 如果自旋失敗,則升級為重量級鎖。