天天看點

synchronized的鎖更新過程

作者:duktig

部落格:https://duktig.cn

優秀還努力。願你付出甘之如饴,所得歸于歡喜。

背景

synchronized

在多線程和高并發中經常用到,尤其是

synchronized

在JDK6之後進行了優化,效率堪比Lock鎖,甚至猶有過之。也是面試中的常客。

面試官:你平時用過多線程嗎?

面試官:你怎麼解決線程安全問題呢?

我:最常用的就是讓多線程的操作同步處理,常用的有

Lock

鎖和

synchronized

面試官:那你能說說

Lock

鎖和

synchronized

的差別嗎?

我:(心裡想,這個比較容易)……

面試官:你能和所說

synchronized

的原理嗎?

我:(這個之前剛好研究過)……

面試官:

synchronized

以前是重量級鎖,之後有了很多優化,你能說說嗎?

我:

synchronized

在JDK6有了很多優化,最重要的有個鎖更新的過程,提高了獲得鎖和釋放鎖的過程。

面試官:那你能說說

synchronized

鎖更新的過程嗎?以及它是怎麼進行辨別的?

我:(GG,這個之前看了很多次,但是都沒理清楚)…… (說的一塌糊塗)

本篇文章就以清晰的流程來闡述

synchronized

的鎖更新過程,以及是怎麼進行辨別的。

synchronized與對象頭

synchronized用的鎖是存在Java對象頭裡的。如果對象是數組類型,則虛拟機用3個字寬(Word)存儲對象頭,如果對象是非數組類型,則用2字寬存儲對象頭。

Java對象頭的長度:

synchronized的鎖更新過程

Java對象頭裡的Mark Word裡預設存儲對象的HashCode、分代年齡和鎖标記位。

Java對象頭的存儲結構:

synchronized的鎖更新過程

在運作期間,Mark Word裡存儲的資料會随着鎖标志位的變化而變化。Mark Word可能變化為存儲以下4種資料:

32位Mark Word的狀态變化:

synchronized的鎖更新過程

在64位虛拟機下,Mark Word是64bit大小的,其存儲結構如表:

synchronized的鎖更新過程

synchronized的鎖更新過程

鎖更新過程依次為:無鎖狀态、偏向鎖、輕量級鎖、重量級鎖。

synchronized可以進行鎖更新,但是不能降級,目的是為了提高獲得鎖和釋放鎖的效率。

偏向鎖

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

對象頭:

  • 23bit的線程ID;
  • 1bit是否為偏向鎖值為1說明是偏向鎖狀态;
  • 無鎖和偏向鎖2bit的鎖标志位都是01

偏向鎖加鎖過程:

  1. 檢查對象頭和棧幀中的鎖記錄是否存儲了目前線程的線程ID
    1. 即簡單測試一下對象頭的Mark Word裡是否存儲着指向目前線程的偏向鎖即可
    2. (為了保證目前線程每次進入和退出同步塊時不需要CAS操作加鎖和解鎖)
  2. 測試成功表示獲得了鎖
  3. 測試失敗再測試一下Mark Word中偏向鎖的辨別是否設定成1(表示目前是偏向鎖)
    1. 如果沒有設定,則使用CAS競争鎖
    2. 如果設定了,則嘗試使用CAS将對象頭的偏向鎖指向目前線程

偏向鎖撤銷過程:

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

  1. 前提條件:等待全局安全點(在這個時間點上沒有正在執行的位元組碼)
  2. 暫停擁有偏向鎖的線程,然後檢查持有偏向鎖的線程是否活着
  3. 如果線程不處于活動狀态,則将對象頭設定成無鎖狀态
  4. 如果線程仍然活着,擁有偏向鎖的棧會被執行,周遊偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼重新偏向于其他線程,要麼恢複到無鎖或者标記對象不适合作為偏向鎖
  5. 最後喚醒暫停的線程

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

synchronized的鎖更新過程

偏向鎖的更新:

隻要存在兩個線程的并發競争,偏向鎖更新為輕量級鎖。

輕量級鎖

輕量級鎖加鎖過程:

  1. 線程在執行同步塊前,JVM在目前線程棧幀中配置設定空間,并将對象頭中的Mark Word複制到鎖記錄中(官方稱為Displaced Mark Word)
  2. CAS将對象頭中的Mark Word替換為指向鎖記錄的指針
  3. 如果成功,目前線程獲得鎖
  4. 如果失敗,表示其他線程競争鎖,目前線程便嘗試使用自旋來擷取鎖

輕量級鎖撤銷過程:

  1. CAS操作将Displaced Mark Word替換回到對象頭
  2. 如果成功,則表示沒有競争發生
  3. 如果失敗,表示目前鎖存在競争,鎖就會膨脹成重量級鎖

輕量級鎖膨脹的流程圖:

synchronized的鎖更新過程

重量級鎖

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖更新成重量級鎖,就不會再恢複到輕量級鎖狀态。

當鎖處于重量級鎖狀态下,其他線程試圖擷取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之後會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之争。

鎖的優缺點對比

synchronized的鎖更新過程

參看:

  • 《Java并發程式設計的藝術》
  • 《Java多線程程式設計核心技術》
  • 曾經總結的多線程相關的筆記