【Java併發】偏向鎖、輕量級鎖、重量級鎖的區別

Synchronized一直是多線程併發編程中的重要角色,但是在Java1.6中,爲了減少獲得鎖帶來的性能消耗,引入了偏向鎖和輕量級鎖。

目錄

鎖的狀態:

偏向鎖

輕量級鎖

重量級鎖

偏向鎖、輕量級鎖、重量級鎖應用場景


鎖的狀態:

  • 無鎖狀態
  • 偏向鎖狀態
  • 輕量級鎖狀態
  • 重量級鎖狀態

四種狀態會隨着競爭的情況逐漸升級,而且是不可逆的過程,即不可降級。

要注意的是,這四種狀態都不是Java語言中的鎖,而是Jvm爲了提高鎖的獲取與釋放效率而做的優化(使用synchronized時)。 
首先通過一個小例子來解釋一下三種鎖的區別:

假如家裏只有一個碗,當我自己在家時,沒有人會和我爭碗,這時即爲偏向鎖狀態 
當我和女朋友都在家吃飯時,如果女朋友不是很餓,則她會等我吃完再用我的碗去吃飯,這就是輕量級鎖狀態 
當我和女朋友都很餓的時候,這時候就會去爭搶這唯一的一個碗(貧窮的我)吃飯,這就是重量級鎖狀態

偏向鎖

偏向鎖的使用原因與作用: 
大部分時候,並不是存在多個多個線程的競爭,而是單個線程多次獲得,爲了更少的代價所以引入偏向鎖,偏向鎖被獲得後會記錄訪問的線程ID,如果下一次還是這個線程申請獲得,則直接執行代碼塊,省去了大量的加鎖解鎖時間。

偏向鎖獲取過程:

  1. 訪問Mark Word中偏向鎖的標識是否設置成1,鎖標誌位是否爲01,確認爲可偏向狀態。 
    (Mark Word介紹)

  2. 如果爲可偏向狀態,則測試線程ID是否指向當前線程,如果是,進入步驟5,否則進入步驟3。

  3. 如果線程ID並未指向當前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark 
    Word中線程ID設置爲當前線程ID,然後執行5;如果競爭失敗,執行4。

  4. 如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(safepoint)時獲得偏向鎖的線程被掛起, 偏向鎖升級爲輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼。(撤銷偏向鎖的時候會導致stop the word)

  5. 執行同步代碼。

CAS介紹

 

釋放偏向鎖的過程: 
1. 當其他線程嘗試競爭偏向鎖時,就會釋放鎖,鎖的撤銷,需要等待全局安全點,分爲以下幾個步驟: 
2. 暫停擁有偏向鎖的線程,檢查線程是否存活 
3. 處於非活動狀態,則設置爲無鎖狀態 
4. 存活,則重新偏向於其他線程或者恢復到無鎖狀態或者標記對象不適合作爲偏向鎖喚醒線程

偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態,撤銷偏向鎖後恢復到未鎖定(標誌位爲“01”)或輕量級鎖(標誌位爲“00”)的狀態。

偏向鎖的適用場景: 
始終只有一個線程在執行同步塊,在它沒有執行完釋放鎖之前,沒有其它線程去執行同步塊,在鎖無競爭的情況下使用,一旦有了競爭就升級爲輕量級鎖,升級爲輕量級鎖的時候需要撤銷偏向鎖,撤銷偏向鎖的時候會導致stop the world操作; 
在有鎖的競爭時,偏向鎖會多做很多額外操作,尤其是撤銷偏向鎖的時候會導致進入安全點,安全點會導致stw,導致性能下降,這種情況下應當禁用;

輕量級鎖

輕量級鎖是由偏向鎖升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級爲輕量級鎖;

輕量級鎖的加鎖過程: 
在代碼進入同步塊的時候,JVM首先將在當前線程的棧幀中建立一個用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱之爲 Displaced Mark Word。然後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。如果成功,則當前線程獲得鎖,如果失敗,表明其他線程獲得競爭鎖,當前線程開始自旋。 
(自旋介紹)

輕量級鎖的釋放過程: 
釋放鎖時,先使用CAS操作將Dispalyedlaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。 
如果失敗,表示當前鎖存在競爭,鎖就會膨脹爲重量級鎖。

重量級鎖

重量級鎖特點:

其他線程試圖獲取鎖時,都會被阻塞,只有持有鎖的線程釋放鎖之後纔會喚醒這些線程,進行競爭。

偏向鎖、輕量級鎖、重量級鎖應用場景

  • 偏向鎖:只有一個線程進入臨界區;
  • 輕量級鎖:多個線程交替進入臨界區
  • 重量級鎖:多個線程同時進入臨界區。

對於偏向鎖的升級場景,我們可以理解爲,臨界區A只有線程1在執行,線程2進入後若發現偏向鎖偏向線程1,但是線程1不存在了或者不再使用該對象鎖了,則線程2將自己的線程ID寫上去,獲得該偏向鎖,然後執行,此時該鎖仍爲偏向鎖。若發現偏向鎖偏向線程1,但是線程1存在並且還在使用該對象鎖,說明開始存在兩個線程的競爭了,此時,暫停線程1,然後該鎖升級爲輕量級鎖,線程1繼續執行,線程2同時開始自旋。

對於輕量級鎖的升級場景,我們可以理解爲,臨界區A只有線程1在執行,線程2進入後發現線程1在執行,線程2開始自旋等待,若自旋很久達到一個閾值後還沒有得到鎖,則升級爲重量級鎖。

(以上觀點爲個人總結分析得到,有可能存在不對的地方,請指正!)

 

參考資料:《Java併發編程的藝術》

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