重大發現,AQS加鎖機制竟然跟Synchronized有驚人的相似

在并發多線程的情況下,為了保證數據安全性 , 一般我們會對數據進行加鎖 , 通常使用Synchronized或者ReentrantLock同步鎖 。Synchronized是基于JVM實現 , 而ReentrantLock是基于Java代碼層面實現的 , 底層是繼承的AQS 。
AQS全稱AbstractQueuedSynchronizer,即抽象隊列同步器,是一種用來構建鎖和同步器的框架 。
我們常見的并發鎖ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier都是基于AQS實現的,所以說不懂AQS實現原理的,就不能說了解Java鎖 。
當我仔細研究AQS底層加鎖原理,發現竟然跟Synchronized加鎖原理有驚人的相似 。讓我突然想到一句名言,記不清怎么說了,意思是框架底層原理很相似,大家多學習底層原理 。
Synchronized的加鎖流程在前幾篇文章已經詳細講過,沒看過一塊再溫習一下 。
1. Synchronized加鎖流程我們先想一下Synchronized的加鎖需求,如果讓你設計Synchronized的對象鎖存儲結構 , 該怎么設計?

  1. 多個線程執行到Synchronized代碼塊,只有一個線程獲取鎖,然后執行同步代碼塊(需要記錄哪個線程獲取了對象鎖) 。
  2. 其他線程被阻塞(被阻塞的線程,是不是可以用鏈表設計個阻塞隊列?)
  3. 持有鎖的線程調用wait方法,釋放鎖,等待被喚醒(等待的線程,是不是可以用鏈表設計個等待隊列?) 。
  4. 被阻塞的線程開始競爭鎖
  5. 調用notify方法,喚醒等待的線程,被喚醒的線程進入阻塞隊列,一塊競爭鎖 。
上面描述了Synchronized的加鎖流程,Synchronized的對象鎖存儲結構是不是跟咱們想的一樣?實際就是的 。
下面是對象鎖的存儲數據結構(由C++實現):
ObjectMonitor() {_header= NULL;_count= 0;_waiters= 0,_recursions= 0;_object= NULL;_owner= NULL; // 持有鎖的線程_WaitSet= NULL; // 等待隊列,存儲處于wait狀態的線程_WaitSetLock= 0 ;_Responsible= NULL ;_succ= NULL ;_cxq= NULL ;FreeNext= NULL ;_EntryList= NULL ; // 阻塞隊列,存儲處于等待鎖block狀態的線程_SpinFreq= 0 ;_SpinClock= 0 ;OwnerIsThread = 0 ;}
重大發現,AQS加鎖機制竟然跟Synchronized有驚人的相似

文章插圖
上圖展示了對象鎖的基本工作機制:
  1. 當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList隊列中阻塞 。
  2. 當某個線程獲取到對象的對象鎖后進入臨界區域,并把對象鎖中的 _owner變量設置為當前線程 , 即獲得對象鎖 。
  3. 若持有對象鎖的線程調用 wait() 方法,將釋放當前持有的對象鎖,_owner變量恢復為null,同時該線程進入 _WaitSet 集合中等待被喚醒 。
  4. 在_WaitSet集合中的線程被喚醒,會被再次放到_EntryList隊列中,重新競爭獲取鎖 。
  5. 若當前線程執行完畢也將釋放對象鎖并復位變量的值,以便其他線程進入獲取鎖 。
Synchronized對象鎖存儲結構和加鎖流程,竟然跟咱們想的一樣 。
再看一下AQS的存儲結構和加鎖流程 , 有沒有相似的地方 。
2. AQS加鎖原理先分析一下,我們使用AQS的加鎖需求:
  1. 多個線程執行到acquire方法的時候,只有一個線程獲取鎖 , 然后執行同步代碼塊(需要記錄哪個線程獲取了對象鎖) 。
  2. 其他線程被阻塞(被阻塞的線程,是不是可以用鏈表設計個阻塞隊列?名叫”同步隊列“?)
  3. 持有鎖的線程調用await方法,釋放鎖,等待被喚醒(等待的線程,是不是可以用鏈表設計個等待隊列?名叫”條件隊列“?) 。
  4. 被阻塞的線程開始競爭鎖
  5. 調用signal方法,喚醒等待的線程,被喚醒的線程進入阻塞隊列,一塊競爭鎖 。
AQS的需求跟Synchronized一模一樣 。
我們再看一下AQS實際的加鎖機制是怎么設計的?是不是跟Synchronized相似?
重大發現,AQS加鎖機制竟然跟Synchronized有驚人的相似

文章插圖
AQS的加鎖流程并不復雜,只要理解了同步隊列和條件隊列,以及它們之間的數據流轉,就算徹底理解了AQS 。
  1. 當多個線程競爭AQS鎖時,如果有個線程獲取到鎖,就把ower線程設置為自己
  2. 沒有競爭到鎖的線程 , 在同步隊列中阻塞(同步隊列采用雙向鏈表,尾插法) 。
  3. 持有鎖的線程調用await方法,釋放鎖 , 追加到條件隊列的末尾(條件隊列采用單鏈表,尾插法) 。
  4. 持有鎖的線程調用signal方法,喚醒條件隊列的頭節點,并轉移到同步隊列的末尾 。
  5. 同步隊列的頭節點優先獲取到鎖
可以看到AQS和Synchronized的加鎖流程幾乎是一模一樣的,AQS中同步隊列就是Synchronized中EntryList,AQS中條件隊列就是Synchronized中的waitSet , 兩個隊列之間的數據轉移流程也是一樣的 。

推薦閱讀