併發編程--偏向鎖、輕量級鎖

鎖的升級與對比

Java SE 1.6爲了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,在 Java SE 1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀 態和重量級鎖狀態,這幾個狀態會隨着競爭情況逐漸升級。鎖可以升級但不能降級,意味着偏 向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提高 獲得鎖和釋放鎖的效率。

偏向鎖

大多數情況下,鎖不僅不存在多線程競爭,而且總是由同 一線程多次獲得,爲了讓線程獲得鎖的代價更低而引入了偏向鎖。

當一個線程訪問同步塊並 獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,以後該線程在進入和退出 同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word裏是否 存儲着指向當前線程的偏向鎖。

  • 如果測試成功,表示線程已經獲得了鎖。
  • 如果測試失敗,則需 要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):
    • 如果沒有設置,則 使用CAS競爭鎖;
    • 如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖的撤銷:

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時, 持有偏向鎖的線程纔會釋放鎖。

偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有正 在執行的字節碼)。

  • 它會首先暫停擁有偏向鎖的線程,然後檢查持有偏向鎖的線程是否活着:
    • 如果線程不處於活動狀態,則將對象頭設置成無鎖狀態;
    • 如果線程仍然活着,擁有偏向鎖的棧 會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼重新偏向於其他 線程,要麼恢復到無鎖或者標記對象不適合作爲偏向鎖,最後喚醒暫停的線程。

偏向鎖的獲得和撤銷流程:

在這裏插入圖片描述

注意: 偏向鎖在Java 6和Java 7裏是默認啓用的,但是它在應用程序啓動幾秒鐘之後才激活,如有必要可以使用JVM參數來關閉延遲:-XX:BiasedLockingStartupDelay=0

如果你確定應用程序裏所有的鎖通常情況下處於競爭狀態,可以通過JVM參數關閉偏向鎖:-XX:- UseBiasedLocking=false,那麼程序默認會進入輕量級鎖狀態。

輕量級鎖

輕量級鎖加鎖:

線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱爲Displaced Mark Word。然後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針:

  • 如果成功,當前線程獲得鎖;
  • 如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

輕量級鎖解鎖:

輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭:

  • 如果成功,則表示沒有競爭發生;
  • 如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

下圖爲兩個線程同時爭奪鎖,導致鎖膨脹的流程圖。
在這裏插入圖片描述

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

鎖的優缺點對比

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章