天天看點

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

接上一篇:剖析 Java并發關鍵字synchronized以及優化繼續說一下鎖膨脹具體過程中的三種鎖。

1. 偏向鎖

1.1 引入的目的

​ 為了在無多線程競争的情況下盡量減少不必要的輕量級鎖執行路徑。因為輕量級鎖的擷取及釋放依賴多次CAS原子指令,而偏向鎖隻需要在置換ThreadID的時候依賴一次CAS原子指令。

1.2 執行流程

擷取鎖:

  1. 檢測Mark Word是否為可偏向狀态,即是否為偏向鎖1,鎖辨別位為01;
  2. 若為可偏向狀态,則測試線程ID是否為目前線程ID,如果是,則執行步驟(5),否則執行步驟(3);
  3. 如果線程ID不為目前線程ID,則通過CAS操作競争鎖,競争成功,則将Mark Word的線程ID替換為目前線程ID,否則執行線程(4);
  4. 通過CAS競争鎖失敗,證明目前存在多線程競争情況,當到達全局安全點,獲得偏向鎖的線程被挂起,偏向鎖更新為輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼塊;
  5. 執行同步代碼塊。

釋放鎖:

​ 偏向鎖的釋放采用了隻有競争才會釋放鎖的機制,線程不會主動釋放偏向鎖,需要等待其他線程來競争。

  1. 暫停擁有偏向鎖的線程,判斷鎖對象石是否還處于被鎖定狀态;
  2. 撤銷偏向鎖,恢複到無鎖狀态(01)或者輕量級鎖的狀态。
當鎖競争比較激烈時,偏向鎖會首先膨脹為輕量級鎖。

2. 輕量級鎖

2.1引入的目的

​ 在沒有多線程競争的前提下,減少傳統的重量級鎖使用作業系統互斥量産生的性能消耗(多指時間消耗)。

2.2 執行流程

獲得鎖:

  1. 判斷目前對象是否處于無鎖狀态(hashcode、0、01),若是,則JVM首先将在目前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的Mark Word的拷貝;
  2. JVM利用CAS操作嘗試将對象的Mark Word更新為指向Lock Record的指針,如果成功表示競争到鎖,則将鎖标志位變成00(表示此對象處于輕量級鎖狀态),執行同步操作;如果失敗則執行步驟(3);
  3. 判斷目前對象的Mark Word是否指向目前線程的棧幀,如果是則表示目前線程已經持有目前對象的鎖,則直接執行同步代碼塊;否則隻能說明該鎖對象已經被其他線程搶占了,這時輕量級鎖需要膨脹為重量級鎖,鎖标志位變成10,後面等待的線程将會進入阻塞狀态。

釋放鎖:

  1. 取出在擷取輕量級鎖儲存在Displaced Mark Word中的資料;
  2. 用CAS操作将取出的資料替換目前對象的Mark Word中,如果成功,則說明釋放鎖成功,否則執行(3);
  3. 如果CAS操作替換失敗,說明有其他線程嘗試擷取該鎖,則需要在釋放鎖的同時需要喚醒被挂起的線程。

長時間的自旋操作是非常消耗資源的,一個線程持有鎖,其他線程就隻能在原地空耗CPU,執行不了任何有效的任務,這種現象叫做忙等(busy-waiting)。如果多個線程用一個鎖,但是沒有發生鎖競争,或者發生了很輕微的鎖競争,那麼synchronized就用輕量級鎖,允許短時間的忙等現象。這是一種折衷的想法,短時間的忙等,換取線程在使用者态和核心态之間切換的開銷。

如果存在多個線程同一時間通路同一鎖的場合,就會導緻輕量級鎖膨脹為重量級鎖。

3. 重量級鎖

​ 重量級鎖是指當有一個線程擷取鎖之後,其餘所有等待擷取該鎖的線程都會處于阻塞狀态。

​ 如果鎖競争情況嚴重,某個達到最大自旋次數的線程,會将輕量級鎖更新為重量級鎖(依然是CAS修改鎖标志位,但不修改持有鎖的線程ID)。當後續線程嘗試擷取鎖時,發現被占用的鎖是重量級鎖,則直接将自己挂起(而不是忙等),等待将來被喚醒。

​ 簡言之,就是所有的控制權都交給了作業系統,由作業系統來負責線程間的排程和線程的狀态變更。而這樣會出現頻繁地對線程運作狀态的切換,線程的挂起和喚醒,進而消耗大量的系統資源。