首頁  >  文章  >  Java  >  Java中的synchronized鎖定膨脹機制怎麼實現

Java中的synchronized鎖定膨脹機制怎麼實現

WBOY
WBOY轉載
2023-04-28 17:13:131508瀏覽

    synchronized

    在JDK 1.5 時,synchronized 需要呼叫監視器鎖(Monitor)來實現,監視器鎖定本質上又是依賴底層的作業系統的Mutex Lock(互斥鎖)實現的,互斥鎖在進行釋放和獲取的時候,需要從用戶態轉換到內核態,這樣就造成了很高的成本,也需要較長的執行時間,這種依賴作業系統Mutex Lock 實作的鎖我們稱之為「重量級鎖」。

    什麼是使用者態和核心態?

    使用者狀態(User Mode):當進程在執行使用者自己的程式碼時,則稱為使用者執行態。核心態(Kernel Mode):當一個任務(進程)執行系統呼叫而陷入核心程式碼中執行時,我們就稱進程處於核心運行態,此時處理器處於特權級最高的核心程式碼中執行。 

    Java中的synchronized鎖定膨脹機制怎麼實現

    為什麼分內核狀態與使用者狀態?

    假設沒有內核態和用戶態之分,程式就可以隨意讀寫硬體資源了,例如隨意讀寫和分配內存,這樣如果程式設計師一不小心將不適當的內容寫到了不該寫的地方,很可能就會導致系統崩潰。

    而有了用戶態和內核態的區分之後,程式在執行某個操作時會進行一系列的驗證和檢驗之後,確認沒問題之後才可以正常的操作資源,這樣就不會擔心一不小心就把系統搞壞的情況了,也就是有了內核態和用戶態的區分之後可以讓程式更加安全的運行,但同時兩種形態的切換會導致一定的性能開銷。

    鎖定膨脹

    在JDK 1.6 時,為了解決取得鎖定和釋放鎖定所帶來的效能消耗,引入了「偏向鎖定」和「輕量級鎖定」的狀態,此時synchronized 的狀態總共有以下4 個:

    • 無鎖定

    • ##偏向鎖定

    Java中的synchronized鎖定膨脹機制怎麼實現

    #輕量級鎖定

    重量級鎖定

    # 鎖定的等級依照上述先後順序依序升級,我們把這個升級的過程稱之為「鎖膨脹」。

     

    PS:到現在為止,鎖定的升級是單向的,也就是說只能從低到高升級(無鎖定-> ; 偏向鎖-> 輕量級鎖定-> 重量級鎖),不會有鎖定降級的情況。

      鎖定膨脹為什麼能優化 synchronized 的效能?當我們了解了這些鎖狀態之後自然就會有答案,下面我們一起來看。
    • 偏向鎖定

    • HotSpot 作者經過研究實踐發現,在大多數情況下,鎖不存在多線程競爭,總是由同一線程多次獲得的,為了讓線程獲得鎖的代價更低,於是引進了偏向鎖。
    • 偏向鎖(Biased Locking)指的是,它會偏向第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下會給線程加一個偏向鎖。

    • 偏向鎖定執行流程
    • 當一個執行緒存取同步程式碼區塊並取得鎖定時,會在物件頭的Mark Word 裡儲存鎖偏向的執行緒ID,在執行緒進入和退出同步區塊時不再透過CAS 操作來加鎖和解鎖,而是檢測Mark Word 裡是否儲存指向當前線程的偏向鎖,如果Mark Word 中的線程ID 和訪問的線程ID 一致,則可以直接進入同步區塊進行程式碼執行,如果執行緒ID 不同,則使用CAS 嘗試取得鎖,如果取得成功則進入同步區塊執行程式碼,否則會將鎖的狀態升級為輕量級鎖定。

    • 偏向鎖的優點

    偏向鎖是為了在無多執行緒競爭的情況下,盡量減少不必要的鎖切換而設計的,因為鎖的取得及釋放要依賴多次CAS 原子指令,而偏向鎖只需要在置換線程ID 的時候執行一次CAS 原子指令即可。

      Mark Word 擴充知識:記憶體佈局
    • 在HotSpot 虛擬機器中,物件在記憶體中儲存的佈局可以分為以下3 個區域:

    • 物件頭(Header)
    • 實例資料(Instance Data)

    對齊填充(Padding)

    Java中的synchronized鎖定膨脹機制怎麼實現##物件頭中又包含了:

    ############Mark Word(標記欄位):我們的偏向鎖定資訊就是儲存在此區域的###。 ############Klass Pointer(Class 物件指標)################物件在記憶體中的佈局如下: ######### #######

     在 JDK 1.6 中預設是開啟偏向鎖定的,可以透過「-XX:-UseBiasedLocking=false」指令來停用偏向鎖定。

    輕量級鎖定

    引入輕量級鎖定的目的是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖定使用作業系統Mutex Lock(互斥鎖)產生的性能消耗。如果使用 Mutex Lock 每次取得鎖和釋放鎖的操作都會帶來使用者態和核心態的切換,這樣系統的效能開銷是很大的。

    當關閉偏向鎖或多個執行緒競爭偏向鎖時就會導致偏向鎖升級為輕量級鎖,輕量級鎖的取得和釋放都透過CAS 完成的,其中鎖取得可能會透過一定次數的自旋來完成。

    注意事項

    需要強調一點:輕量級鎖定並不是用來取代重量級鎖定的,它的本意是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖定使用產生的性能消耗。輕量級鎖所適應的場景是執行緒交替執行同步區塊的情況,如果同一時間多個執行緒同時存取時,就會導致輕量級鎖定膨脹為重量級鎖定。

    重量級鎖定

    synchronized 是依賴監視器Monitor 實作方法同步或程式碼區塊同步的,程式碼區塊同步使用的是monitorenter 和monitorexit 指令來實現的,monitorenter 指令是在編譯後插入到同步程式碼區塊的開始位置,而monitorexit 是插入到方法結束處和異常處的,任何物件都有一個Monitor 與之關聯,當且一個Monitor 被持有後,它將處於鎖定狀態。

    如下加鎖程式碼:

    public class SynchronizedToMonitorExample {
        public static void main(String[] args) {
            int count = 0;
            synchronized (SynchronizedToMonitorExample.class) {
                for (int i = 0; i < 10; i++) {
                    count++;
                }
            }
            System.out.println(count);
        }
    }

    當我們將上述程式碼編譯成字節碼之後,它的內容是這樣的: 

    Java中的synchronized鎖定膨脹機制怎麼實現

    從上述結果可以看出,在main 方法的執行中多個monitorenter 和monitorexit 的指令,由此可知synchronized 是依賴Monitor 監視器鎖實現的,而監視器鎖又是依賴作業系統的互斥鎖(Mutex Lock),互斥鎖在每次取得和釋放鎖時,都會帶來用戶態和核心態的切換,這樣就增加了系統的效能開銷。

    以上是Java中的synchronized鎖定膨脹機制怎麼實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除