1. 程式人生 > >Java鎖的升級策略 偏向鎖 輕量級鎖 重量級鎖

Java鎖的升級策略 偏向鎖 輕量級鎖 重量級鎖

這三種鎖是指鎖的狀態,並且是專門針對Synchronized關鍵字。JDK 1.6 為了減少"重量級鎖"的效能消耗,引入了“偏向鎖”和“輕量級鎖”,鎖一共擁有4種狀態:無鎖狀態、偏向鎖、輕量級鎖、重量級鎖。鎖狀態是通過物件頭的Mark Word來進行標記的:

鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖後不能降級成偏向鎖,這種鎖升級卻不能降級的策略,是為了提高獲得鎖和釋放鎖的效率

  1. 重量級鎖:依賴於底層作業系統的Mutex Lock,執行緒會被阻塞住
    • 缺點:加鎖和解鎖需要從使用者態切換到核心態,效能消耗較大
  2. 輕量級鎖:基於重量級鎖進行了優化(避免上下文切換,提高了效能),它假設多執行緒競爭是互相錯開的,不會發生執行緒阻塞,呢麼上下文切換就是多餘的
    • 第一個特點:採用了CAS操作加鎖和解鎖,由於輕量級鎖的鎖記錄(Lock Record)是存放在物件頭和執行緒空間裡的,因此加鎖和解鎖不需要上下文切換,效能消耗較小
    • 第二個特點:一旦發生多執行緒競爭,首先基於“自旋鎖”思想,自旋CPU迴圈等待一段時間,不會發生上下文切換,如果還是無法獲得鎖,就將鎖升級為重量級鎖
  3. 偏向鎖:基於輕量級鎖進行了優化(減少多次的加鎖和解鎖,提高了效能),它假設整個過程只有一個執行緒獲得鎖,呢麼多次的加鎖和解鎖就是多餘的
    • 特點:在第一次獲得鎖之後不會釋放鎖,它會一直持有鎖,後續進入鎖時只需檢查一下鎖狀態和偏向執行緒ID是否為自己,從而省去了多次的加鎖和解鎖

1.偏向鎖

獲取鎖:

  1. 檢測物件頭的Mark Word是否為可偏向狀態(即是否為偏向鎖1,鎖標誌位是否為01),如果不是,嘗試競爭鎖:嘗試CAS操作將Mark Word的執行緒ID設定為當前執行緒ID,以表示執行緒獲得鎖,如果失敗說明鎖已被佔用
  2. 若為可偏向狀態,則檢查執行緒ID是否為當前執行緒ID,如果是則表示當前執行緒已經持有鎖(鎖的可重入),否則說明鎖已被佔用
  3. 如果鎖已被佔用,只能撤銷偏向鎖為無鎖狀態或輕量級鎖

釋放鎖:(偏向鎖使用了一種等到競爭出現才釋放鎖的機制,執行緒是不會主動釋放偏向鎖的,只有當其他執行緒競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖)

  1. 偏向鎖的撤銷需要等待全域性安全點(在這個時間點沒有正在執行的位元組碼),暫停擁有偏向鎖的執行緒,檢查持有偏向鎖的執行緒是否還活著
  2. 如果執行緒掛了,則將物件頭設定成無鎖狀態;如果執行緒仍然活著,則將物件頭設定為輕量級鎖(鎖的升級),最終輕量級鎖一定會被釋放

2.輕量級鎖

獲取鎖:

  1. 檢測物件頭的Mark Word是否為輕量級鎖(鎖標誌位為00),如果不是,嘗試競爭鎖:JVM首先在當前執行緒的棧幀中建立一個鎖記錄(Lock Record),用於備份儲存物件頭的Mark Word(官方把這份拷貝加了一個Displaced字首,稱為Displaced Mark Word),然後JVM嘗試CAS操作將Mark Word更新為指向Lock Record的指標,以表示執行緒獲得鎖,如果失敗說明鎖已被佔用
  2. 若為輕量級鎖,判斷物件頭的Mark Word是否指向當前執行緒的棧幀的Lock Record,如果是則表示當前執行緒已經持有鎖(鎖的可重入),否則說明鎖已被佔用
  3. 如果鎖已被佔用,當前執行緒便嘗試自旋CPU來獲取鎖,自旋一定次數後輕量級鎖會膨脹為重量級鎖(鎖標誌位變成10),執行緒進入阻塞

釋放鎖:

  1. 嘗試CAS操作將Displaced Mark Word中替換回物件頭,如果成功,說明輕量級鎖釋放成功
  2. 如果CAS操作失敗,說明存在鎖競爭,鎖已經膨脹成重量級鎖,需要在釋放鎖的同時喚醒那些被掛起的執行緒

3.重量級鎖

重量級鎖依賴於底層作業系統的Mutex Lock,所有執行緒都會被阻塞住,執行緒之間的切換需要從使用者態到核心態,切換成本非常高。

總結:鎖的優缺點對比

<
優點 缺點 適用場景
偏向鎖(Biased Lock) 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距 如果執行緒間存在鎖競爭,會帶來額外的鎖撤銷 適用於只有一個執行緒訪問
輕量級鎖(Lightweight Lock) 競爭的執行緒不會阻塞,提高了程式的響應速度 對於得不到鎖的執行緒,自旋會消耗CPU 追求響應時間,或者要求臨界區簡短,自旋不會佔用CPU過久
重量級鎖(Heavyweight Lock) 執行緒競爭不使用自旋,不會消耗CPU資源 執行緒阻塞,響應時間緩慢 追求吞吐量