Java面試知識點(四十五)多線程中 的各種鎖(補充)

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

  • 悲觀鎖:重量鎖(典型代表synchronized)
  • 樂觀鎖:偏向鎖、輕量級鎖、自旋鎖

自旋鎖

【定義】

自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那麼那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖後即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗。

但是線程自旋是需要消耗 CPU 的,說白了就是讓 CPU 在做無用功,如果一直獲取不到鎖,那線程也不能一直佔用 CPU 自旋做無用功,所以需要設定一個自旋等待的最大時間。

如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。

【自旋鎖的優缺點】

自旋鎖儘可能的減少線程的阻塞,這對於鎖的競爭不激烈,且佔用鎖時間非常短的代碼塊來說性能能大幅度的提升,因爲自旋的消耗會小於線程阻塞掛起再喚醒的操作的消耗,這些操作會導致線程發生兩次上下文切換!

但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間佔用鎖執行同步塊,這時候就不適合使用自旋鎖了,因爲自旋鎖在獲取鎖前一直都是佔用 cpu 做無用功,佔着 XX 不 XX,同時有大量線程在競爭一個鎖,會導致獲取鎖的時間很長,線程自旋的消耗大於線程阻塞掛起操作的消耗,其它需要 cpu 的線程又不能獲取到 cpu,造成 cpu 的浪費。所以這種情況下我們要關閉自旋鎖;


偏向鎖

【定義】

Java 偏向鎖 (Biased Locking) 是 Java6 引入的一項多線程優化。
偏向鎖,顧名思義,它會偏向於第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下,就會給線程加一個偏向鎖。

如果在運行過程中,遇到了其他線程搶佔鎖,則持有偏向鎖的線程會被掛起,JVM 會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。

它通過消除資源無競爭情況下的同步原語,進一步提高了程序的運行性能。

【偏向鎖的實現】

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

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

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

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

    • 執行同步代碼。


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

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


輕量級鎖

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

總結

上面幾種鎖都是 JVM 自己內部實現,當我們執行 synchronized 同步塊的時候 jvm 會根據啓用的鎖和當前線程的爭用情況,決定如何執行同步操作;

在所有的鎖都啓用的情況下線程進入臨界區時會先去獲取偏向鎖,如果已經存在偏向鎖了,則會嘗試獲取輕量級鎖,啓用自旋鎖,如果自旋也沒有獲取到鎖,則使用重量級鎖,沒有獲取到鎖的線程阻塞掛起,直到持有鎖的線程執行完同步塊喚醒他們;

這是synchronized在jdk1.5之後的優化

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