一文講清楚synchronized原理---每週一更系列

枫叶藏在眼眸發表於2024-08-03

synchronized是Java提供的原子性內建鎖,這種內建的並且使用者看不到的鎖也被稱為監視器鎖。

synchronized透過在程式碼塊前後加上monitorenter和monitorexit位元組碼指令用於實現進入和退出。

如果是同步方法,則是打上標記,隱式的使用monitorenter和monitorexit位元組碼指令。

在jdk1.5之前,加鎖和釋放鎖是由作業系統來實現的,系統呼叫涉及到上下文切換,效能消耗極高,多以當時synchronized效能極差並且是公認的重量級鎖。

在jdk1.6之後,引入了偏向鎖和輕量級鎖兩個概念,透過鎖升級策略來解決效能問題。

為了講清楚Synchronized工作原理,需要理解以下幾個問題:

  • 程式 = 資料結構 + 演算法,鎖也是屬於一種程式,所以它應該有對應的資料結構 + 演算法。

  • Java物件記憶體佈局,即synchronized使用了怎麼樣的資料結構?

    每個Java物件在記憶體中的佈局通常分為三部分:物件頭、例項資料、對齊填充。物件頭中包含Mark Word和Class Point

    重點就是Mark Word,它包含一下資訊:

    • 雜湊碼

    • GC標誌

    • 鎖標誌位:判斷當前物件處於什麼鎖狀態(無鎖、偏向鎖、輕量級鎖、重量級鎖)

    • 偏向鎖的執行緒ID

    • 輕量級鎖的棧地址指標

    • 重量級鎖的Monitor指標

  • 怎麼理解每個物件都有成為鎖的潛質?

    由於每個物件都有Mark Word,都可以儲存鎖狀態,都是可以支撐鎖演算法的資料結構,所以每個物件都有成為鎖的潛質。

  • 物件是怎麼透過Mark Word成為一把鎖的?即怎麼使用這個資料結構標識不同的鎖狀態?

    無鎖:當一個物件新建時,沒有被任何物件鎖定,此時鎖標誌位01

    偏向鎖:第一個執行緒獲取物件鎖,鎖標誌位01,偏向標誌位標記為1,並在物件頭中記錄鎖的執行緒ID,如果沒有競爭,下次直接根據ID直接獲取,即可重入,避免了頻繁CAS。

    輕量級鎖:發現鎖競爭,即兩個執行緒以上時,偏向鎖取消,升級為輕量級鎖,此時執行緒會在棧幀中建立一個鎖記錄,並嘗試透過CAS將物件頭的輕量級鎖棧地址指標指向鎖記錄,成功則獲取到了鎖。鎖記錄中記錄了重入次數,同時修改鎖標誌位,記錄鎖狀態。

    重量級鎖:當競爭激烈,CAS次數超過重試閾值時,JVM決定升級為重量級鎖,此時JVM建立Monitor物件或者使用已有的Monitor物件,並將Monitor指標指向Monitor物件,後續交由作業系統進行排程。

    Monitor物件

    Monitor物件不是原始Java物件,而是JVM內部使用的資料結構。它用於實現重量級鎖的管理,通常包含以下欄位:

    1. Owner:當前持有鎖的執行緒。
    2. Recursion Count:重入計數器,記錄當前執行緒的重入次數。
    3. Entry List:等待獲取鎖的執行緒佇列。
    4. Wait Set:等待某個條件的執行緒佇列(通常用於waitnotify機制)。

    執行緒進入同步程式碼塊時,首先進入entryList,獲取到鎖後就更改Owner,並且計數器加1。

    如果執行緒wait了,就會釋放鎖,釋放鎖之後就進入Wait Set,只有透過notify或者notifyAll才能喚醒,喚醒之後又進入entryList排隊競爭鎖。

    獲取到鎖的執行緒執行完畢就釋放鎖,計數器減1,當前執行緒Owner置Null

相關文章