1. 程式人生 > >精進之路之AQS及相關元件

精進之路之AQS及相關元件

AQS ( AbstractQueuedSynchronizer)是一個用來構建鎖和同步器的框架,使用AQS能簡單且高效地構造出應用廣泛的大量的同步器,比如我們提到的ReentrantLock,Semaphore,其他的諸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基於AQS的。當然,我們自己也能利用AQS非常輕鬆容易地構造出符合我們自己需求的同步器。

1.思維導圖:

 2.原理

2.1.AQS核心思想

AQS核心思想是,如果被請求的共享資源空閒,則將當前請求資源的執行緒設定為有效的工作執行緒,並且將共享資源設定為鎖定狀態。如果被請求的共享資源被佔用,那麼就需要一套執行緒阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH佇列鎖實現的,即將暫時獲取不到鎖的執行緒加入到佇列中。

注:CLH(Craig,Landin,and Hagersten)佇列是一個虛擬的雙向佇列(虛擬的雙向佇列即不存在佇列例項,僅存在結點之間的關聯關係)。AQS是將每條請求共享資源的執行緒封裝成一個CLH鎖佇列的一個結點(Node)來實現鎖的分配。

 

AQS使用一個int成員變數來表示同步狀態,通過內建的FIFO佇列來完成獲取資源執行緒的排隊工作。AQS使用CAS對該同步狀態進行原子操作實現對其值的修改。

/**
* The synchronization state.
*/
private volatile int state;


狀態資訊通過procted型別的getState,setState,compareAndSetState進行操作

//返回同步狀態的當前值
protected final int getState() { return state; }
// 設定同步狀態的值
protected final void setState(int newState) { state = newState; }
//原子地(CAS操作)將同步狀態值設定為給定值update如果當前同步狀態的值等於expect(期望值)
protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update);

2.2 AQS 對資源的共享方式

AQS定義兩種資源共享方式

    Exclusive(獨佔):只有一個執行緒能執行,如ReentrantLock。又可分為公平鎖和非公平鎖:
        公平鎖:按照執行緒在佇列中的排隊順序,先到者先拿到鎖
        非公平鎖:當執行緒要獲取鎖時,無視佇列順序直接去搶鎖,誰搶到就是誰的
    Share(共享):多個執行緒可同時執行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我們都會在後面講到。

ReentrantReadWriteLock 可以看成是組合式,因為ReentrantReadWriteLock也就是讀寫鎖允許多個執行緒同時對某一資源進行讀。

不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現時只需要實現共享資源 state 的獲取與釋放方式即可,至於具體執行緒等待佇列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在上層已經幫我們實現好了。

2.3 AQS底層使用了模板方法模式

同步器的設計是基於模板方法模式的,如果需要自定義同步器一般的方式是這樣(模板方法模式很經典的一個應用):

    使用者繼承AbstractQueuedSynchronizer並重寫指定的方法。(這些重寫方法很簡單,無非是對於共享資源state的獲取和釋放)
    將AQS組合在自定義同步元件的實現中,並呼叫其模板方法,而這些模板方法會呼叫使用者重寫的方法。

AQS使用了模板方法模式,自定義同步器時需要重寫下面幾個AQS提供的模板方法:
isHeldExclusively()//該執行緒是否正在獨佔資源。只有用到condition才需要去實現它。
tryAcquire(int)//獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。
tryRelease(int)//獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryAcquireShared(int)//共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。
tryReleaseShared(int)//共享方式。嘗試釋放資源,成功則返回true,失敗則返回false。
預設情況下,每個方法都丟擲 UnsupportedOperationException。 這些方法的實現必須是內部執行緒安全的,並且通常應該簡短而不是阻塞。AQS類中的其他方法都是final ,所以無法被其他類使用,只有這幾個方法可以被其他類使用。

以ReentrantLock為例,state初始化為0,表示未鎖定狀態。A執行緒lock()時,會呼叫tryAcquire()獨佔該鎖並將state+1。此後,其他執行緒再tryAcquire()時就會失敗,直到A執行緒unlock()到state=0(即釋放鎖)為止,其它執行緒才有機會獲取該鎖。當然,釋放鎖之前,A執行緒自己是可以重複獲取此鎖的(state會累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多麼次,這樣才能保證state是能回到零態的。

再以CountDownLatch以例,任務分為N個子執行緒去執行,state也初始化為N(注意N要與執行緒個數一致)。這N個子執行緒是並行執行的,每個子執行緒執行完後countDown()一次,state會CAS(Compare and Swap)減1。等到所有子執行緒都執行完後(即state=0),會unpark()主呼叫執行緒,然後主呼叫執行緒就會從await()函式返回,繼續後餘動作。

一般來說,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支援自定義同步器同時實現獨佔和共享兩種方式,如ReentrantReadWriteLock。

3 Semaphore(訊號量)-允許多個執行緒同時訪問

synchronized 和 ReentrantLock 都是一次只允許一個執行緒訪問某個資源,Semaphore(訊號量)可以指定多個執行緒同時訪問某個資源。具體解釋和應用可參見 深入淺出java Semaphore    

 

4.ountDownLatch (倒計時器) 具體參考  https://blog.csdn.net/qq_34337272/article/details/83655291  

 

非常感謝原作者的分享