1. 程式人生 > 實用技巧 >Java中鎖優化

Java中鎖優化

鎖優化

鎖優化技術

  • 自旋鎖和自適應自旋
  • 鎖消除
  • 鎖粗化
  • 偏向鎖
  • 輕量級鎖

自旋鎖和自適應自旋

當一個執行緒獲取鎖失敗時,不是直接放棄處理器時間,而是執行一定次數自旋。若自旋期間釋放鎖,則直接獲取鎖,否則再掛起執行緒。
問題:自旋的時間過長,浪費處理器資源。自旋時間過短,可能無法獲得鎖。

自適應自旋

自旋的次數由同一個鎖上的自旋時間及鎖的擁有者狀態決定。
若同一個鎖物件上,自旋等待剛剛獲取鎖,則延長自旋的次數。
若自旋很少獲取到鎖,則可能省略自旋階段。


鎖消除

通過逃逸分析發現某些使用同步的程式碼段,對不存在競爭的共享資料進行加鎖,可以將這種鎖消除。
若一段程式碼中,堆中的所有資料都不會逃逸出被其他執行緒訪問,則將其當作棧上資料,不進行加鎖。

示例:

public void method(){
	StringBuilder builder = new StringBuilder();
    String s = builder.toString();
}

鎖粗化

一般情況下,儘量減少同步塊的作用範圍,從而減少鎖競爭。
但是對於一系列操作對於同一個物件進行加鎖,解鎖。即使沒有執行緒競爭,頻繁的加鎖和釋放鎖會導致不必要的效能消耗。
鎖粗化:消除對同一個物件的連續的加鎖和釋放鎖,將鎖的同步範圍擴大到整個加鎖和解鎖範圍,即只需要一次加鎖和一次解鎖。


輕量級鎖

目的:在沒有多執行緒競爭的情況下,減少重量級鎖使用系統互斥量造成的效能消耗。

物件頭組成

  • 物件自身的執行時資料:(Mark Word)
    • 雜湊碼
    • GC年齡
    • 鎖標誌位
    • 偏向狀態
  • 指向方法區物件型別的指標

鎖狀態:

  • 未鎖定
  • 輕量級鎖
  • 重量級鎖
  • GC標記
  • 可偏向

流程:

加鎖:

  • 若物件沒有被鎖定,則先在當前執行緒的棧幀中建立鎖記錄(儲存物件的Mark Word的拷貝)。
  • 使用CAS將物件的Mark Word更新為指向呼叫棧中的鎖記錄的指標。
  • 若更新成功,該執行緒持有該物件的鎖,更新物件的鎖標誌位為輕量級鎖。
  • 若更新失敗,則先檢查物件的Mark Word是否指向當前執行緒的棧幀。若是,則當前執行緒已持有鎖,則繼續執行。否則存線上程競爭,將鎖膨脹為重量級鎖。Mark Word儲存的是指向重量級鎖的指標(互斥量),後續操作必須進入阻塞狀態。

解鎖:

  • 通過CAS將物件的當前的Mark Word替換為執行緒棧中的備份的鎖記錄。
  • 若替換成功,則順利結束。
  • 若替換失敗,則由其他執行緒嘗試獲取鎖,則釋放鎖的同時,喚醒被掛起的執行緒。

適用於:
沒有競爭的情況,可以避免使用互斥量。但是若存在鎖競爭,則除了互斥量的開銷,還有CAS操作的開銷。


偏向鎖

偏向鎖是在無鎖情況下將所有同步都消除。
這個鎖會偏向於第一個獲取的執行緒。

流程:

加鎖:

  • 鎖物件第一次被執行緒獲取時,將物件頭中鎖狀態設定為偏向鎖,偏向模式設定為1,表示進入偏向狀態。
  • 使用CAS將執行緒ID記錄到物件的Mark Work中。
  • 如果操作成功,則持有偏向鎖的執行緒每次進入同步程式碼塊中時不需要任何同步操作。
  • 若另外一個執行緒嘗試獲取鎖,則根據當前鎖定狀態改變鎖狀態:
    • 若物件未鎖定:將物件的鎖狀態設定為未鎖定,不可偏向狀態。
    • 若物件已鎖定:將物件的鎖狀態設定為輕量級鎖狀態。

注:

  • 一旦計算過雜湊值或者從偏向狀態轉向其他狀態,則無法再轉向偏向狀態。

物件頭(Mark Word)

鎖狀態 內容 鎖標誌位
未鎖定 物件雜湊碼,GC年齡 01
輕量級鎖 指向呼叫棧中的鎖記錄的指標 00
重量級鎖 指向重量級鎖的指標 10
GC標記 11
可偏向 執行緒ID,GC年齡 01