Java并發編程 | Synchronized原理與使用

Java提供了多種機制實現多線程之間有需要同步執行的場景需求 。其中最基本的是Synchronized ,實現上使用對象監視器( Monitor ) 。
Java中的每個對象都是與線程可以鎖定或解鎖的對象監視器( Monitor )關聯 。在同一時間只有一個線程可以在對象監視器( Monitor )上保持鎖定 。任何其他線程試圖鎖定對象監視器( Monitor )都會被阻止,直到它們可以獲得該監視器上的鎖定 。
Synchronized 基本使用方式Synchronized 的作用范圍,依據鎖定的對象(object、this、class)、使用方式,可以分成五種情況 。如果按照JVM字節碼的區別 , 也可以分成兩種形式:代碼塊(monitorenter、monitorexit)、函數(ACC_SYNCHRONIZED) 。
雖然可以按照不同維度來劃分 Synchronized 但本身機制是一樣的,無論是 Synchronized 函數/代碼塊,都是通過對象監視器( Monitor )來實現 。無論是this、class、object本質上都是一個對象,區別無非代表的是當前實例、類、一般實例,它們都有著對象監視器( Monitor ) 。
在HotSpot虛擬機中,對象監視器( Monitor ) 具體的實現類就是 ObjectMonitor(C++) 。
在使用/分析 Synchronized 同步是否有效正確的時候 , 只需要分析需要的同步塊是否作用在同一個對象監視器( Monitor )上 。換一種描述,是否作用在同一個對象(Object)上,這里(Object)可以是this、object、class 。

Java并發編程 | Synchronized原理與使用

文章插圖
下面分別按照Synchronized 代碼塊、Synchronized 函數維度來進行詳細介紹 。
Synchronized 代碼塊Synchronized 代碼塊的一般使用形式:synchronized ( Expression ) Block。
Expression 必須是一個對象,可以是class、this、object , 不能是原始類型(int、float...);否則編譯的時候就會報錯 。如果 Expression 是null,會拋出NullPointerException 的異常 。
Block表示一段邏輯代碼,執行邏輯代碼前會鎖定Expression 的對象監視器( Monitor ) 。如果正常運行完成后 , 對象監視器( Monitor )會被釋放;如果運行期間異常/中斷了同樣的也會釋放對象監視器( Monitor ) 。先加鎖確保其他線程無法進入執行,所以Synchronized 是悲觀鎖,JVM指令上使用monitorenter、monitorexit 來進行相關實現 。
Java并發編程 | Synchronized原理與使用

文章插圖
在字節碼指令里可以也可以看到有兩個monitorexit ,一個是正常運行后的釋放;另一個是在異常(athrow)拋出前的釋放 。同一個線程可以多次進入被鎖定的相同對象監視器( Monitor ),所以Synchronized 是可重入鎖 。
【Java并發編程 | Synchronized原理與使用】
Java并發編程 | Synchronized原理與使用

文章插圖
Synchronized 函數Synchronized函數在同步原理上同 Synchronized代碼塊是沒有區別的,都是通過鎖定對象監視器( Monitor );區別在于這里的對象是隱藏了起來 。同樣的支持可重入 。
如果是靜態方法(static),鎖定的對象是這個方法所在的class object 對象 。如果是普通的方法 , 鎖定的是this(當前實例)對象 。
編譯成JVM字節碼的時候 , 函數描述上會標識ACC_SYNCHRONIZED ,并不會在函數代碼塊中顯示的使用monitorenter、monitorexit指令 。
在調用函數前鎖定對象監視器( Monitor ),完成運行后釋放對象監視器( Monitor ) 。無論函數是否有顯性的拋出/處理異常,如果有異常中斷拋出前也會自動的釋放鎖定的對象監視器( Monitor ) 。
Java并發編程 | Synchronized原理與使用

文章插圖
Synchronized 實現原理Synchronized同步一直也在進行優化,也是跟隨著JDK新理念一起發展 。比如偏向鎖、輕量鎖、重量鎖、適應性自旋等等機制 。不同的JDK版本,不同的JVM可能都有所不同 。
在HotSpot虛擬機中,拋開鎖升級、自適應等機制;基本原理是線程通過 CAS搶占對象監視器( Monitor ) _Owner來實現鎖 , 沒有搶占的會進入 _EntryList 來進行放置 。當然,線程執行/中斷釋放_Owner后,_EntryList并不是簡單按照FIFO來進行選擇執行不會保證公平性,所以Synchronized是非公平鎖 。
Java并發編程 | Synchronized原理與使用

文章插圖
圖中_WaitSet沒有體現用途,但其是很重要的一個結構, 用于當 _Owner 執行線程中斷時,線程將會寫入 。值得注意獲取到鎖之后才能中斷,等待鎖時不可中斷 。當相關線程被喚醒后,會采有不同的策略重新回到_EntryList 或者 參與CAS競爭 _Owner , 這里存在線程上下文切換的可能 。

推薦閱讀