首頁  >  文章  >  Java  >  Java中的Synchronized原理是什麼

Java中的Synchronized原理是什麼

王林
王林轉載
2023-05-31 14:55:141740瀏覽

    原始碼層級剖析Synchronized

    #物件結構

    Synchronized是Java中的隱式鎖,它的擷取鎖定與釋放鎖定都是隱含的,完全交由JVM幫助我們操作,在了解Synchronized關鍵字之前,首先要學習的知識點就是Java的物件結構,因為Synchronized鎖定就是存放在Java物件中的,Java物件結構如下圖所示:

    Java中的Synchronized原理是什麼

    可以清楚的看到Java物件由三個部分組成,分別是物件頭、實例資料、填充數據,我們的鎖定就存放在物件頭中,接下來我們將對物件結構做一個簡單的解析:

    • mark-down:物件標記字段佔8個字節,用於存儲有關鎖的標記位等信息,從圖中可以看出有雜湊值、輕量級鎖的標記位、偏向鎖標記位等。

    • Klass Pointer:Class物件的類型指針,它就是指向目前物件屬於哪個Class類的指針,jdk1.8預設開啟壓縮指針後佔用4個位元組,關閉壓縮指針後佔用8個位元組。

    • 物件實際資料:這部分內容包含物件的所有成員變量,大小由各個成員變數決定,例如byte佔用1個位元組、int佔用4個位元組等。

    • 對其填入:這部分內容只是做到空間補全,就是一個佔位符的作用,因為HotSpot虛擬機的記憶體管理系統要求物件的起始位址必須是8位元組的整數倍,因此如果出現物件實例沒有對齊的話,就需要透過對其填充來補充。

    在mark-down鎖定類型標記中,可以看到總共有五種類型,分別是無鎖定、偏向鎖定、輕量級鎖定、重量級鎖定、GC標記,所以如果只是使用2位元標記是無法完全被表示出來的,所以引入了一位偏向鎖標記,也就是說001為無鎖、101為偏向鎖。

    Monitor 物件

    上面介紹了物件結構,可以看到在Mark-down中會儲存不同的鎖定訊息,當鎖的狀態為重量級鎖(10)時,Mark-down中會存放一個指向Monitor物件的指針,這個Monitor物件也稱為監視器鎖定

    synchronized的運作機制,就是JVM偵測到共享物件存在不同的競爭狀況的時候,會自動切換到適合的鎖定實現,這種切換就是鎖定的升級、降級。 (很多地方都說鎖只能升級,不能降級,其實這種說法是錯誤的,在《Java並發編程的藝術》書中說到,對於偏向鎖來說,它可以進行降級到無鎖狀態,也叫做偏向鎖的撤銷)。

    目前有三種不同的Monitor實現,分別是偏向鎖、輕量級鎖和重量級鎖。當一個線程持有一個Monitor時,它就獲得了鎖。

    Java中的Monitor是基於C 的ObjectMonitor實現的,它的主要成員包括:

    • _owner:指向持有ObjectMonitor物件的執行緒

    • #_WaitSet:存放處於wait狀態的執行緒佇列,也就是呼叫wait()方法的執行緒

    • _EntryList:存放處於等待鎖定Block狀態的執行緒佇列

    • _count:約為_WaitSet _EntryList的節點數總和

    • _cxq:多個執行緒爭搶鎖,會先存入這個單向鍊錶

    • _recursions:記錄重入次數

    • #_object:儲存的Monitor物件

    取得Monitor物件的執行緒進入_owner區的時候,_count 1,如果執行緒呼叫了wait()方法,那麼就會釋放Monitor物件(釋放鎖定),_owner恢復為空同時_count-1。此時該執行緒進入_WaitSet佇列中,等待被喚醒。

    從上述的描述可以看出,synchronized關鍵字取得鎖的關鍵在於每個物件的物件頭中,這也就能解釋了為什麼synchronized()括號裡存放任何物件都能獲得鎖的特徵。

    Synchronized特徵

    原子性

    原子性,就是說一個操作要麼完成,要麼不完成,不存在完成一半的情況,也就是說這個操作是不可中斷的。

    synchronized可以保證在同一時間內只有一個線程拿到鎖,進入到程式碼區塊去執行程式碼,這樣說如果不能理解,那麼就想像下面的一個場景,有一個廁所只有一個坑位,並且廁所還上鎖了,就是為了防止多人一起上廁所的不文明現象,每個人上廁所都必須要去廁所管理員處繳費,繳費後才能拿到鎖再去上廁所,上完廁所再把要是還給廁所管理員,synchronized就是廁所管理員,保證一次只能有一個人拿到鎖,並且每個人用完廁所之後都必須要歸還鑰匙。

    接下來看到下面一個同步加上方法:

    public static void add() {
        synchronized (Demo.class) {
            counter++;
        }
    }

    將其進行反編譯後查看程式碼:

    javap -v -p Demo

    public static void add();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC , ACC_SYNCHRONIZED
        Code:
          stack=2, locals=2, args_size=0
             0: ldc           #12                 // class
             2: dup
             3: astore_0
             4: monitorenter
             5: getstatic     #10                 // Field counter:I
             8: iconst_1
             9: iadd
            10: putstatic     #10                 // Field counter:I
            13: aload_0
            14: monitorexit
            15: goto          23
            18: astore_1
            19: aload_0
            20: monitorexit
            21: aload_1
            22: athrow
            23: return
          Exception table:

    可以看到有兩個指令很明顯和monitor有關:

    • monitorenter:在判斷擁有同步識別ACC_SYNCHRONIZED 搶先進入此方法的執行緒會優先擁有Monitor 的owner ,此時計數器1

    • #monitorexit:當執行完退出後,計數器-1,歸0 後被其他進入的線程獲得

    可見性

    可見性指的是當多個線程訪問同一個變量時,一個線程修改了這個變數的值,其他的線程能夠立刻感知,並且能看到修改後的值。線程的可見性與JMM密切相關。在接下來的文章中,我們將探討如何使用volatile關鍵字來實現可見性

    而Synchronized擁有可見性,因為它加鎖和釋放鎖都有如下語義:

    • 執行緒加鎖前,必須清空工作記憶體中共享變數的值,從而從主記憶體讀取最新的共享變數的值。

    • 執行緒釋放鎖定時,必須把共享變數的值刷新到主記憶體中。

    • synchronized的可見性依賴作業系統核心互斥鎖實現,相當於JVM中的lock,unlock,退出程式碼區塊時需要刷新共享變數到主記憶體中,這一點和volatile關鍵字不一樣,volatile關鍵字的可見度是依賴記憶體屏障(也叫記憶體柵欄)來實現的。

    有序性

    as-if-serial,就是確保不管編譯器和處理器為了效能最佳化怎樣進行指令重排序,都需要保證單執行緒下的運行結果的正確性。也就是常說的:如果在本線程內觀察,所有的操作都是有序的如果在一個線程觀察另一個線程,所有的操作都是無序的

    注意,這裡的有序性和volatile是不一樣的,它並不是volatile的防止指令重新排序。

    可重入鎖

    可重入鎖的概念很簡單,就是一個執行緒可以多次取得自己所持有的物件鎖,這種鎖就是可重入鎖,同樣的釋放鎖也就需要釋放相同數量的鎖。在synchronized鎖定物件中,有一個計數器用來記錄取得鎖定的次數,即重入次數。

    鎖定升級的過程

    synchronized 鎖定有四種交替升級的狀態:無鎖定、偏向鎖定、輕量級鎖定和重量級,這幾個狀態隨著競爭情況逐漸升級,後續會補上一張完整的鎖升級圖

    以上是Java中的Synchronized原理是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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