synchronized作為Java程式員最常用同步工具,很多人卻對它的用法和實作原理一知半解,以至于還有不少人認為synchronized是重量級鎖,性能較差,盡量少用,
但不可否認的是synchronized依然是并發首選工具,連volatile、CAS、ReentrantLock都無法動搖synchronized的地位,synchronized是作業面試中的必備技能,今天就跟著一燈一塊深入剖析synchronized的底層原理,
1. synchronized作用
synchronized是Java提供一種隱式鎖,無需開發者手動加鎖釋放鎖,保證多執行緒并發情況下資料的安全性,實作了同一個時刻只有一個執行緒能訪問資源,其他執行緒只能阻塞等待,簡單說就是互斥同步,
2. synchronized用法
先看一下synchronized有哪幾種用法?
| 使用位置 | 被鎖物件 | 示例代碼 |
|---|---|---|
| 實體方法 | 實體物件 |
public synchronized void method() { …… } |
| 靜態方法 | class類 |
public static synchronized void method() { …… } |
| 實體物件 | 實體物件 |
public void method() { Object obj = new Object(); synchronized (obj) { …… } } |
| 類物件 | class類 |
public void method() { synchronized (Demo.class) { …… } } |
| this關鍵字 | 實體物件 |
public void method() { synchronized (this) { …… } } |
可以看到被鎖物件只要有兩種,實體物件和class類,
-
由于靜態方法可以通過類名直接訪問,所以它跟直接加鎖在class類上是一樣的,
-
當在實體方法、實體物件、this關鍵字上面加鎖的時候,鎖定范圍都是當前實體物件,
-
實體物件上面的鎖和class類上面的鎖,兩者不互斥,
3. synchronized加鎖原理
當我們使用synchronized在方法和物件上加鎖的時候,Java底層到底怎么實作加鎖的?
當在類物件上加鎖的時候,也就是在class類加鎖,代碼如下:
/**
* @author 一燈架構
* @apiNote Synchronized示例
**/
public class SynchronizedDemo {
public void method() {
synchronized (SynchronizedDemo.class) {
System.out.println("Hello world!");
}
}
}
反編譯一下,看一下原始碼實作:

可以看到,底層是通過monitorenter和monitorexit兩個關鍵字實作的加鎖與釋放鎖,執行同步代碼之前使用monitorenter加鎖,執行完同步代碼使用monitorexit釋放鎖,拋出例外的時候也是用monitorexit釋放鎖,
寫成偽代碼,類似下面這樣:
/**
* @author 一燈架構
* @apiNote Synchronized示例
**/
public class SynchronizedDemo {
public void method() {
try {
monitorenter 加鎖;
System.out.println("Hello world!");
monitorexit 釋放鎖;
} catch (Exception e) {
monitorexit 釋放鎖;
}
}
}
當在實體方法上加鎖,底層是怎么實作的呢?代碼如下:
/**
* @author 一燈架構
* @apiNote Synchronized示例
**/
public class SynchronizedDemo {
public static synchronized void method() {
System.out.println("Hello world!");
}
}
再反編譯看一下底層實作:

這次只使用了一個ACC_SYNCHRONIZED關鍵字,實作了隱式的加鎖與釋放鎖,其實無論是ACC_SYNCHRONIZED關鍵字,還是monitorenter和monitorexit,底層都是通過獲取monitor鎖來實作的加鎖與釋放鎖,
而monitor鎖又是通過ObjectMonitor來實作的,虛擬機中ObjectMonitor資料結構如下(C++實作的):
ObjectMonitor() {
_header = NULL;
_count = 0; // WaitSet 和 EntryList 的節點數之和
_waiters = 0,
_recursions = 0; // 重入次數
_object = NULL;
_owner = NULL; // 持有鎖的執行緒
_WaitSet = NULL; // 處于wait狀態的執行緒,會被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 多個執行緒爭搶鎖,會先存入這個單向鏈表
FreeNext = NULL ;
_EntryList = NULL ; // 處于等待鎖block狀態的執行緒,會被加入到該串列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}

圖上展示了ObjectMonitor的基本作業機制:
-
當多個執行緒同時訪問一段同步代碼時,首先會進入 _EntryList 佇列中等待,
-
當某個執行緒獲取到物件的Monitor鎖后進入臨界區域,并把Monitor中的 _owner 變數設定為當前執行緒,同時Monitor中的計數器 _count 加1,即獲得物件鎖,
-
若持有Monitor的執行緒呼叫 wait() 方法,將釋放當前持有的Monitor鎖,_owner變數恢復為null,_count減1,同時該執行緒進入 _WaitSet 集合中等待被喚醒,
-
在_WaitSet 集合中的執行緒會被再次放到_EntryList 佇列中,重新競爭獲取鎖,
-
若當前執行緒執行完畢也將釋放Monitor并復位變數的值,以便其他執行緒進入獲取鎖,
執行緒爭搶鎖的程序要比上面展示得更加復雜,除了_EntryList 這個雙向鏈表用來保存競爭的執行緒,ObjectMonitor中還有另外一個單向鏈表 _cxq,由兩個佇列來共同管理并發的執行緒,

下篇再講一下Synchronized鎖優化的程序,
我是「一燈架構」,如果本文對你有幫助,歡迎各位小伙伴點贊、評論和關注,感謝各位老鐵,我們下期見

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/518462.html
標籤:Java
