Java鎖詳解

weixin_33866037發表於2018-03-02

鎖的狀態總共有四種:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。這幾個狀態會隨著競爭情況逐漸升級。為了提高獲得鎖和釋放鎖的效率,鎖可以升級但不能降級。

偏向鎖

為了在無多執行緒競爭的情況下減少執行緒獲得鎖的代價,引入了偏向鎖的概念。

偏向鎖只需要在切換執行緒ID的時候進行CAS操作,之後該執行緒在進入和退出同步塊的時候不再需要進行CAS操作來加鎖與解鎖。因此偏向鎖可以提高單一執行緒多次操作同步塊的效能。

執行緒B要使用一個同步物件的時候,會查詢物件頭裡的Mark Word的鎖狀態是否為可偏向狀態。如果是,測試執行緒ID是否指向本執行緒。

  • 如果是,則表示執行緒B已經獲得鎖了,執行同步程式碼即可。
  • 如果不是,則通過CAS操作來競爭偏向鎖(首先會暫停擁有偏向鎖的執行緒A,檢查持有偏向鎖的執行緒A是否活著,如果執行緒A不處於活動狀態,則競爭成功;如果執行緒A仍然存活,則表明存在競爭關係,競爭失敗。)
    • 如果競爭成功,則將Mark Word設定為無鎖狀態,然後將Mark Word重新設定偏向鎖,Thread ID指向執行緒B
    • 如果競爭失敗,則表明有競爭關係。當到達全域性安全點的時候,獲得偏向鎖的程式A會被掛起,偏向鎖會升級為輕量級鎖,然後被掛起的程式A會繼續向下執行,程式B通過自旋競爭鎖。

偏向鎖只有當有其他執行緒競爭鎖的時候才會釋放,偏向鎖的撤銷會在全域性安全點的時候進行。

輕量級鎖

執行緒執行同步塊之前,會在當前執行緒的棧幀中建立用於儲存鎖記錄的空間,並將物件頭的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。執行緒通過CAS嘗試將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,執行緒獲得鎖;如果失敗,表示有其他執行緒競爭鎖了,當前執行緒會嘗試使用自旋來獲得鎖。

如果自旋失敗,輕量級鎖會膨脹為重量級鎖。

輕量級解鎖時,會使用CAS操作將Displaced替換回到物件頭。如果成功則表示沒有競爭關係,如果失敗(失敗的原因是Mark Word的鎖標誌位被修改導致CAS失敗),表示當前鎖存在競爭關係,且鎖已經被升級為重量級鎖

重量級鎖

重量級鎖下,當執行緒試圖獲取鎖的時候,執行緒會被阻塞,當持有鎖的執行緒釋放鎖之後會喚醒這些執行緒,被喚醒的執行緒之間會進行鎖的爭奪。

鎖狀態之間的轉換概括

如果多個執行緒不存在競爭同步塊的問題,則物件會保持偏向鎖的狀態。當出現兩個執行緒同時競爭同步塊的時候,鎖會升級為輕量級鎖,後來的執行緒會通過自旋等待擁有鎖的程式執行完同步程式碼。如果成功則將Mark Word替換成指向棧中鎖記錄的指標。如果失敗多次則表示有其他執行緒競爭鎖,則膨脹為重量級鎖。

CAS操作

CAS非常重要,理解了CAS的概念才能知道上面的操作成功失敗的真正意義。

CAS原理:CAS有三個運算元,即記憶體值v,舊的運算元a,新的運算元b。當我們需要更新v對應的記憶體地址中的值為b時,首先判斷記憶體中的值是否和我們之前的所見值a相同,若相同則將v對應的記憶體地址儲存的值修改為b,若不同,則什麼都不做。

也可以理解舊運算元為期望值,當期望值與對應記憶體中的值相同時,視為滿足期望。此時才將b值寫入記憶體值v對應的記憶體。

鎖的優缺點對比
優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 如果執行緒間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 適用於只有一個執行緒訪問同步塊場景
輕量級鎖 競爭的執行緒不會阻塞,提高了程式的響應速度。 如果始終得不到鎖競爭的執行緒使用自旋會消耗CPU。 追求響應時間。同步塊執行速度非常快。
重量級鎖 執行緒競爭不使用自旋,不會消耗CPU。 執行緒阻塞,響應時間緩慢。 追求吞吐量。同步塊執行速度較長。

相關文章