Java程序員必會Synchronized底層原理剖析

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!");}}}反編譯一下,看一下源碼實現:
Java程序員必會Synchronized底層原理剖析

文章插圖
可以看到,底層是通過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 釋放鎖;}}}當在實例方法上加鎖,底層是怎么實現的呢?代碼如下:
【Java程序員必會Synchronized底層原理剖析】/** * @author 一燈架構 * @apiNote Synchronized示例 **/public class SynchronizedDemo {public static synchronized void method() {System.out.println("Hello world!");}}再反編譯看一下底層實現:
Java程序員必會Synchronized底層原理剖析

文章插圖
這次只使用了一個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 ;}
Java程序員必會Synchronized底層原理剖析

文章插圖
圖上展示了ObjectMonitor的基本工作機制:
  1. 當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 隊列中等待 。
  2. 當某個線程獲取到對象的Monitor鎖后進入臨界區域 , 并把Monitor中的 _owner 變量設置為當前線程,同時Monitor中的計數器 _count 加1 。即獲得對象鎖 。
  3. 若持有Monitor的線程調用 wait() 方法,將釋放當前持有的Monitor鎖 , _owner變量恢復為null,_count減1,同時該線程進入 _WaitSet 集合中等待被喚醒 。
  4. 在_WaitSet 集合中的線程會被再次放到_EntryList 隊列中,重新競爭獲取鎖 。

    推薦閱讀