精進之路之AQS及相關組件

AQS ( AbstractQueuedSynchronizer)是一個用來構建鎖和同步器的框架,使用AQS能簡單且高效地構造出應用普遍的大量的同步器,好比咱們提到的ReentrantLock,Semaphore,其餘的諸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基於AQS的。固然,咱們本身也能利用AQS很是輕鬆容易地構造出符合咱們本身需求的同步器。java

1.思惟導圖:
安全

 2.原理框架

2.1.AQS核心思想函數

AQS核心思想是,若是被請求的共享資源空閒,則將當前請求資源的線程設置爲有效的工做線程,而且將共享資源設置爲鎖定狀態。若是被請求的共享資源被佔用,那麼就須要一套線程阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH隊列鎖實現的,即將暫時獲取不到鎖的線程加入到隊列中。ui

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

 

AQS使用一個int成員變量來表示同步狀態,經過內置的FIFO隊列來完成獲取資源線程的排隊工做。AQS使用CAS對該同步狀態進行原子操做實現對其值的修改。spa

/**
* 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(信號量)-容許多個線程同時訪問

Semaphore是一個計數信號量。
從概念上將,Semaphore包含一組許可證。
若是有須要的話,每一個acquire()方法都會阻塞,直到獲取一個可用的許可證。
每一個release()方法都會釋放持有許可證的線程,而且歸還Semaphore一個可用的許可證。
然而,實際上並無真實的許可證對象供線程使用,Semaphore只是對可用的數量進行管理維護。

.net

synchronized 和 ReentrantLock 都是一次只容許一個線程訪問某個資源,Semaphore(信號量)能夠指定多個線程同時訪問某個資源。具體解釋和應用可參見 深刻淺出java Semaphore    線程

 

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

 適用場景:

       好比對於馬拉松比賽,進行排名計算,參賽者的排名,確定是跑完比賽以後,進行計算得出的,翻譯成Java識別的預發,就是N個線程執行操做,主線程等到N個子線程執行完畢以後,在繼續往下執行。

其餘參考 : https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483745&idx=2&sn=6778ee954a19816310df54ef9a3c2f8a&chksm=fd985700caefde16b9970f5e093b0c140d3121fb3a8458b11871e5e9723c5fd1b5a961fd2228&token=1829606453&lang=zh_CN#rd

很是感謝原做者的分享

相關文章
相關標籤/搜索