1. 程式人生 > >多執行緒-- 八.J.U.C之AQS

多執行緒-- 八.J.U.C之AQS

AQS

一.AQS的概念:

    lock包下有三個籠統的類:

        AbstractOwnableSynchronizer

        AbstractQueuedLongSynchronizer

        AbstractQueuedSynchronizer

    通常的,AbstractQueuedSynchronizer的簡稱為AQS。一般我們叫AQS抽象佇列同步器。

    AQS定義了一套多執行緒訪問共享資源的同步器框架,許多同步類實現都依賴於它    

    它是J.U.C的核心。

二.AQS的設計:

    1.使用Node實現FIFO(First in First out)佇列,可以用於構建鎖或者其它同步裝置的基礎框架。

    2.利用了一個Int型別表示狀態。

    3.使用方法是繼承。子類通過繼承並通過實現它的方法管理其狀態(acquire和release)的方法操縱狀態。

    4.可以同時實現排它鎖和共享鎖模式。(獨佔,共享)

三.AQS的常用同步元件:

    1.CountDownLatch

    2.Semaphore

    3.CyclicBarrier

    4.ReentrantLock

    5.Condition(基於ReentrantLock的)

    6.FutureTask(它不是AbstractQueuedSynchronizer類的子類)

1.CountDownLatch

    它是一個同步輔助類,通過它可以完成類似於阻塞當前執行緒的功能,換句話說,就是一個或多個執行緒一直等待,直到其它執行緒執行的操作完成。

    它用了一個給定的計數器來進行初始化,該計數器的操作是原子性的,也就是說,同時只能有一個執行緒去操作計數器。

    呼叫該類await方法的執行緒,會一直處於阻塞狀態,直到其它執行緒呼叫countDown方法,使當前計數值的值變為0。

    每次呼叫countDown方法的時候,計數器的值會減一。當計數器的值減到0的時候,所有呼叫await方法而處於等待狀態的執行緒會繼續往下執行。這操作只能出現一次,因為計數器是不能重置的。

    典型應用場景:平行計算。

舉例:

    CountDownLatch countDownLatch = new CountDownLatch(200);

    200表示需要等待執行完畢的執行緒數量。

結果就是,finish是在所有執行緒執行完畢之後執行的。

也可以有複雜的情況,就是每個任務我都給指定時間,超過指定時間沒做完,那也不管了。這也好弄,就是修改await方法

第一個引數是數值,第二個引數是單位。

2.Semaphore

    訊號量。它可以控制併發訪問的執行緒個數。和CountDownLatch使用有些類似,也提供了兩個核心方法。acquire和release.

    acquire方法是讓它獲取一個許可,如果沒有,就等待; release方法是執行完成後,釋放一個許可出來。

    使用場景:常用於僅能提供有限訪問的資源,比如資料庫。資料庫連線數假設只有20,而我們應用的併發數可能會遠遠大於這些。如果同時對資料庫進行操作,有可能會出現因為無法獲取資料庫連線,而導致異常。這個時候我們就可以通過Semaphore控制併發數 

舉例:

上圖只是獲取一個許可,如果我們有需求,需要拿到多個許可才可以執行怎麼辦?

答:也是在方法裡面加引數即可:

以上圖為例,假如我們想,超過3個併發,剩下的不執行了,丟棄,那麼怎麼辦?

答:用semaphore.tryAcquire()方法即可。

3.CyclicBarrier

    也是一個同步輔助類。他允許一組執行緒相互等待直到某個公共的屏障點。

    通過它可以完成多個執行緒之間相互等待,只有當每個執行緒都準備就緒後才能各自繼續往下執行後面的操作。

    和CountDownLatch相似的地方上是,它也是通過計數器實現的。

程式碼舉例實現:

     CyclicBarrier barrier = new CyclicBarrier(5);    這句程式碼告訴我們當前有5個執行緒要同步等待。

     barrier.await();    當達到括號裡給定的數目的時候,這段程式碼後面的部分就可以執行了。

執行結果為:

同樣,這個await方法也是可以傳參的,跟上面一樣,也是傳入時間,意思就是讓它只等待多少時間。

而且,在CyclicBarrier barrier = new CyclicBarrier(5)程式碼裡面,也可以傳入第二個引數runnable介面。例:

    CyclicBarrier barrier = new CyclicBarrier(5,()->{

        log.info("test running");

    });

意思是,在達到屏障點的時候,優先執行裡面的程式碼。

4.ReentrantLock 與 鎖

    Java裡主要分為兩類鎖,一類是synchronized關鍵字修飾的鎖;一類是J.U.C裡提供的鎖。

一.ReentrantLock(可重入鎖)和synchronized的區別

    ①.可重入性

    ②.鎖的實現

        synchronized關鍵字是依賴JVM實現的,ReentrantLock是JDK實現的。

    ③.效能區別

        在synchronized關鍵字優化以前,效能差很多。但是引入了輕量級鎖之後,兩者效能差不多了。

    ④.功能區別

        便利性:synchronized用起來比較方便,是由編譯器決定鎖的加鎖和釋放,不會造成死鎖,JVM自動釋放。而ReentrantLock需要我們手工加鎖釋放鎖。為了避免忘記釋放造成死鎖,最好在finally中宣告。

        鎖的細粒度和靈活度:ReentrantLock要優於synchronized。

二.ReentrantLock獨有的功能

    ①.ReentrantLock可指定是公平鎖還是非公平鎖;而 synchronized只能是非公平鎖。

        公平鎖:先等待的執行緒先獲得鎖。

    ②.ReentrantLock提供了Condition類,可以分組喚醒需要喚醒的執行緒;而synchronized要麼隨機喚醒一個,要麼喚醒全部。

    ③.ReentrantLock提供了能夠中斷等待鎖的執行緒的機制,lock.lockInterruptibly()

    所以,如果想實現這三個功能的時候,是必須要使用ReentrantLock的。

程式碼演示:

Lock lock = new ReentrantLock()建立鎖物件時,可以傳入引數。

原始碼如下:

預設傳入的是個非公平鎖。可以傳入引數true false使其成為公平鎖。

5.StampedLock

    StampedLock控制鎖有三種模式。寫,讀,樂觀讀。重點在樂觀讀上。

    一個StampedLock狀態是由版本和模式兩個部分組成。鎖獲取方法返回的是一個數字,作為票據。0表示沒有寫鎖,讀鎖上分為悲觀鎖和樂觀鎖。

    對吞吐量有巨大改進,效能高一些。  

    舉例: