背景:以前在研究多線程的時候,模模糊糊知道AQS這個東西,可是對於其內部是如何實現,以及具體應用不是很理解,還自認爲多線程已經學習的很到位了,貽笑大方。html
Java併發包基石-AQS詳解
Java併發包(JUC)中提供了不少併發工具,這其中,不少咱們耳熟能詳的併發工具,譬如ReentrangLock、Semaphore,它們的實現都用到了一個共同的基類--AbstractQueuedSynchronizer,簡稱AQS。AQS是一個用來構建鎖和同步器的框架,使用AQS能簡單且高效地構造出應用普遍的大量的同步器,好比咱們提到的ReentrantLock,Semaphore,其餘的諸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基於AQS的。固然,咱們本身也能利用AQS很是輕鬆容易地構造出符合咱們本身需求的同步器。node
private volatile int state;//共享變量,使用volatile修飾保證線程可見性
狀態信息經過procted類型的getState,setState,compareAndSetState進行操做設計模式
AQS支持兩種同步方式:安全
1.獨佔式多線程
2.共享式併發
這樣方便使用者實現不一樣類型的同步組件,獨佔式如ReentrantLock,共享式如Semaphore,CountDownLatch,組合式的如ReentrantReadWriteLock。總之,AQS爲使用提供了底層支撐,如何組裝實現,使用者能夠自由發揮。框架
同步器的設計是基於模板方法模式的,通常的使用方式是這樣:分佈式
1.使用者繼承AbstractQueuedSynchronizer並重寫指定的方法。(這些重寫方法很簡單,無非是對於共享資源state的獲取和釋放)工具
2.將AQS組合在自定義同步組件的實現中,並調用其模板方法,而這些模板方法會調用使用者重寫的方法。oop
這實際上是模板方法模式的一個很經典的應用。
咱們來看看AQS定義的這些可重寫的方法:
protected boolean tryAcquire(int arg) : 獨佔式獲取同步狀態,試着獲取,成功返回true,反之爲false
protected boolean tryRelease(int arg) :獨佔式釋放同步狀態,等待中的其餘線程此時將有機會獲取到同步狀態;
protected int tryAcquireShared(int arg) :共享式獲取同步狀態,返回值大於等於0,表明獲取成功;反之獲取失敗;
protected boolean tryReleaseShared(int arg) :共享式釋放同步狀態,成功爲true,失敗爲false
protected boolean isHeldExclusively() : 是否在獨佔模式下被線程佔用。
關於AQS的使用,咱們來簡單總結一下:
如何使用
首先,咱們須要去繼承AbstractQueuedSynchronizer這個類,而後咱們根據咱們的需求去重寫相應的方法,好比要實現一個獨佔鎖,那就去重寫tryAcquire,tryRelease方法,要實現共享鎖,就去重寫tryAcquireShared,tryReleaseShared;最後,在咱們的組件中調用AQS中的模板方法就能夠了,而這些模板方法是會調用到咱們以前重寫的那些方法的。也就是說,咱們只須要很小的工做量就能夠實現本身的同步組件,重寫的那些方法,僅僅是一些簡單的對於共享資源state的獲取和釋放操做,至於像是獲取資源失敗,線程須要阻塞之類的操做,天然是AQS幫咱們完成了。
設計思想
對於使用者來說,咱們無需關心獲取資源失敗,線程排隊,線程阻塞/喚醒等一系列複雜的實現,這些都在AQS中爲咱們處理好了。咱們只須要負責好本身的那個環節就好,也就是獲取/釋放共享資源state的姿式T_T。很經典的模板方法設計模式的應用,AQS爲咱們定義好頂級邏輯的骨架,並提取出公用的線程入隊列/出隊列,阻塞/喚醒等一系列複雜邏輯的實現,將部分簡單的可由使用者決定的操做邏輯延遲到子類中去實現便可。
AQS的核心思想是基於volatile int state這樣的一個屬性同時配合Unsafe工具對其原子性的操做來實現對當前鎖的狀態進行修改。
AQS的源碼的主要點在於node節點、共享式 獨佔式 阻塞隊列,cas
咱們先來簡單描述下AQS的基本實現,前面咱們提到過,AQS維護一個共享資源state,經過內置的FIFO來完成獲取資源線程的排隊工做。(這個內置的同步隊列稱爲"CLH"隊列)。該隊列由一個一個的Node結點組成,每一個Node結點維護一個prev引用和next引用,分別指向本身的前驅和後繼結點。AQS維護兩個指針,分別指向隊列頭部head和尾部tail。
其實就是個雙端雙向鏈表。
當線程獲取資源失敗(好比tryAcquire時試圖設置state狀態失敗),會被構形成一個結點加入CLH隊列中,同時當前線程會被阻塞在隊列中(經過LockSupport.park實現,實際上是等待態)。當持有同步狀態的線程釋放同步狀態時,會喚醒後繼結點,而後此結點線程繼續加入到對同步狀態的爭奪中。
ps:此處能夠參考
(轉)分佈式系統互斥性與冪等性問題的分析與解決
Node結點是AbstractQueuedSynchronizer中的一個靜態內部類,咱們撿Node的幾個重要屬性來講一下
static final class Node { /** waitStatus值,表示線程已被取消(等待超時或者被中斷)*/ static final int CANCELLED = 1; /** waitStatus值,表示後繼線程須要被喚醒(unpaking)*/ static final int SIGNAL = -1; /**waitStatus值,表示結點線程等待在condition上,當被signal後,會從等待隊列轉移到同步到隊列中 */ /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** waitStatus值,表示下一次共享式同步狀態會被無條件地傳播下去 static final int PROPAGATE = -3; /** 等待狀態,初始爲0 */ volatile int waitStatus; /**當前結點的前驅結點 */ volatile Node prev; /** 當前結點的後繼結點 */ volatile Node next; /** 與當前結點關聯的排隊中的線程 */ volatile Thread thread; /** ...... */ }
ps:static final 和volatile變量
至此,關於acquire的方法源碼已經分析完畢,咱們來簡單總結下
a.首先tryAcquire獲取同步狀態,成功則直接返回;不然,進入下一環節;
b.線程獲取同步狀態失敗,就構造一個結點,加入同步隊列中,這個過程要保證線程安全;
c.加入隊列中的結點線程進入自旋狀態,如果老二結點(即前驅結點爲頭結點),纔有機會嘗試去獲取同步狀態;不然,當其前驅結點的狀態爲SIGNAL,線程即可安心休息,進入阻塞狀態,直到被中斷或者被前驅結點喚醒。
ps:多個線程都經過【CAS+死循環】這個free-lock黃金搭檔來對隊列進行修改,每次可以保證只有一個成功,若是失敗下次重試,若是是N個線程,那麼每一個線程最多loop N次,最終都能夠成功。
當前線程執行完本身的邏輯以後,須要釋放同步狀態,來看看release方法的邏輯
release的同步狀態相對簡單,須要找到頭結點的後繼結點進行喚醒,若後繼結點爲空或處於CANCEL狀態,從後向前遍歷找尋一個正常的結點,喚醒其對應線程。
共享式:共享式地獲取同步狀態。對於獨佔式同步組件來說,同一時刻只有一個線程能獲取到同步狀態,其餘線程都得去排隊等待,其待重寫的嘗試獲取同步狀態的方法tryAcquire返回值爲boolean,這很容易理解;對於共享式同步組件來說,同一時刻能夠有多個線程同時獲取到同步狀態,這也是「共享」的意義所在。其待重寫的嘗試獲取同步狀態的方法tryAcquireShared返回值爲int。
ps:共享式和獨佔市的區別。共享式tryAcquire返回boolean,tryAcquireShared返回int,分一下集中
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
1.當返回值大於0時,表示獲取同步狀態成功,同時還有剩餘同步狀態可供其餘線程獲取;
2.當返回值等於0時,表示獲取同步狀態成功,但沒有可用同步狀態了;
3.當返回值小於0時,表示獲取同步狀態失敗。
獲取同步狀態--acquireShared
代碼邏輯比較容易理解,須要注意的是,共享模式,釋放同步狀態也是多線程的,此處採用了CAS自旋來保證。
關於AQS的介紹及源碼分析到此爲止了。
AQS是JUC中不少同步組件的構建基礎,簡單來說,它內部實現主要是狀態變量state和一個FIFO隊列來完成,同步隊列的頭結點是當前獲取到同步狀態的結點,獲取同步狀態state失敗的線程,會被構形成一個結點(或共享式或獨佔式)加入到同步隊列尾部(採用自旋CAS來保證此操做的線程安全),隨後線程會阻塞;釋放時喚醒頭結點的後繼結點,使其加入對同步狀態的爭奪中。
AQS爲咱們定義好了頂層的處理實現邏輯,咱們在使用AQS構建符合咱們需求的同步組件時,只需重寫tryAcquire,tryAcquireShared,tryRelease,tryReleaseShared幾個方法,來決定同步狀態的釋放和獲取便可,至於背後複雜的線程排隊,線程阻塞/喚醒,如何保證線程安全,都由AQS爲咱們完成了,這也是很是典型的模板方法的應用。AQS定義好頂級邏輯的骨架,並提取出公用的線程入隊列/出隊列,阻塞/喚醒等一系列複雜邏輯的實現,將部分簡單的可由使用者決定的操做邏輯延遲到子類中去實現。