Synchronized是Java中的隱式鎖,它的擷取鎖定與釋放鎖定都是隱含的,完全交由JVM幫助我們操作,在了解Synchronized關鍵字之前,首先要學習的知識點就是Java的物件結構,因為Synchronized鎖定就是存放在Java物件中的,Java物件結構如下圖所示:
可以清楚的看到Java物件由三個部分組成,分別是物件頭、實例資料、填充數據,我們的鎖定就存放在物件頭中,接下來我們將對物件結構做一個簡單的解析:
mark-down:物件標記字段佔8個字節,用於存儲有關鎖的標記位等信息,從圖中可以看出有雜湊值、輕量級鎖的標記位、偏向鎖標記位等。
Klass Pointer:Class物件的類型指針,它就是指向目前物件屬於哪個Class類的指針,jdk1.8預設開啟壓縮指針後佔用4個位元組,關閉壓縮指針後佔用8個位元組。
物件實際資料:這部分內容包含物件的所有成員變量,大小由各個成員變數決定,例如byte佔用1個位元組、int佔用4個位元組等。
對其填入:這部分內容只是做到空間補全,就是一個佔位符的作用,因為HotSpot虛擬機的記憶體管理系統要求物件的起始位址必須是8位元組的整數倍,因此如果出現物件實例沒有對齊的話,就需要透過對其填充來補充。
在mark-down鎖定類型標記中,可以看到總共有五種類型,分別是無鎖定、偏向鎖定、輕量級鎖定、重量級鎖定、GC標記,所以如果只是使用2位元標記是無法完全被表示出來的,所以引入了一位偏向鎖標記,也就是說001為無鎖、101為偏向鎖。
上面介紹了物件結構,可以看到在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就是廁所管理員,保證一次只能有一個人拿到鎖,並且每個人用完廁所之後都必須要歸還鑰匙。
接下來看到下面一個同步加上方法:
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中文網其他相關文章!