Java多線程進階(九)—— J.U.C之locks框架:AQS共享功能剖析(4)

18cc7f57e258d2abf4607d3e1cd26fc8.jpg

本文首發於一世流雲的專欄: https://segmentfault.com/blog...

1、本章概述

AQS系列的前三個章節,咱們經過ReentrantLock的示例,分析了AQS的獨佔功能。
本章將以CountDownLatch爲例,分析AQS的共享功能。CountDownLatch,是J.U.C中的一個同步器類,可做爲倒數計數器使用,關於CountDownLatch的使用和說明,讀者能夠參考:
Java多線程進階(十八)—— J.U.C之synchronizer框架:CountDownLatchjava

CountDownLatch示例
假設如今有3個線程,ThreadA、ThreadB、mainThread,CountDownLatch初始計數爲1:
CountDownLatch switcher = new CountDownLatch(1);

線程的調用時序以下:segmentfault

//ThreadA調用await()方法等待

//ThreadB調用await()方法等待

//主線程main調用countDown()放行

2、AQS共享功能的原理

1. 建立CountDownLatch

CountDownLatch的建立沒什麼特殊,調用惟一的構造器,傳入一個初始計數值,內部實例化一個AQS子類:多線程

CountDownLatch switcher = new CountDownLatch(1);框架

clipboard.png

能夠看到,初始計數值count其實就是同步狀態值,在CountDownLatch中,同步狀態State表示CountDownLatch的計數器的初始大小。ui

2. ThreadA調用await()方法等待

CountDownLatch的await方法是響應中斷的,該方法實際上是調用了AQS的acquireSharedInterruptibly方法:
clipboard.pngspa

注意tryAcquireShared方法,該方法嘗試獲取鎖,由AQS子類實現,其返回值的含義以下:線程

State 資源的定義
小於0 表示獲取失敗
0 表示獲取成功
大於0 表示獲取成功,且後繼爭用線程可能成功

CountDownLatch中的tryAcquireShared實現至關簡單,當State值爲0時,永遠返回成功:
clipboard.png3d

咱們以前說了在CountDownLatch中,同步狀態State表示CountDownLatch的計數器的初始值,當 State==0時,表示無鎖狀態,且一旦State變爲0,就永遠處於無鎖狀態了,此時全部線程在await上等待的線程均可以繼續執行。
而在ReentrantLock中, State==0時,雖然也表示無鎖狀態,可是隻有一個線程能夠重置State的值。這就是 共享鎖的含義。

好了,繼續向下執行,ThreadA嘗試獲取鎖失敗後,會調用doAcquireSharedInterruptibly
clipboard.pngcode

首先經過addWaiter方法,將ThreadA包裝成共享結點,插入等待隊列,插入完成後隊列結構以下:
clipboard.pngblog

而後會進入自旋操做,先嚐試獲取一次鎖,顯然此時是獲取失敗的(主線程main還未調用countDown,同步狀態State仍是1)。
而後判斷是否要進入阻塞(shouldParkAfterFailedAcquire):
clipboard.png

好了,至此,ThreadA進入阻塞態,最終隊列結構以下:
clipboard.png

3. ThreadB調用await()方法等待

流程和步驟2徹底相同,調用後ThreadB也被加入到等待隊列中:
clipboard.png

4. 主線程main調用countDown()放行

ThreadA和ThreadB調用了await()方法後都在等待了,如今主線程main開始調用countDown()方法,該方法調用後,ThreadA和ThreadB都會被喚醒,並繼續往下執行,達到相似門栓的做用。

來看下countDown方法的內部:
clipboard.png

該方法內部調用了AQS的releaseShared方法,先嚐試一次釋放鎖,tryReleaseShared方法是一個鉤子方法,由CountDownLatch實現,當同步State狀態值首次變爲0時,會返回true:
clipboard.png
clipboard.png

先調用compareAndSetWaitStatus將頭結點的等待狀態置爲0,表示將喚醒後續結點(ThreadA),成功後的等待隊列結構以下:
clipboard.png

而後調用unparkSuccessor喚醒後繼結點(ThreadA被喚醒後會從原阻塞處繼續往下執行,這個在步驟5再講):
clipboard.png

此時,等待隊列結構以下:
clipboard.png

5. ThreadA從原阻塞處繼續向下執行

ThreadA被喚醒後,會從原來的阻塞處繼續向下執行:
因爲是一個自旋操做,ThreadA會再次嘗試獲取鎖,因爲此時State同步狀態值爲0(無鎖狀態),因此獲取成功。而後調用setHeadAndPropagate方法:
clipboard.png

setHeadAndPropagate方法把ThreadA結點變爲頭結點,並根據傳播狀態判斷是否要喚醒並釋放後繼結點:
clipboard.png

①將ThreadA變成頭結點
clipboard.png

②調用doReleaseShared方法,釋放並喚醒ThreadB結點
clipboard.png

clipboard.png

6. ThreadB從原阻塞處繼續向下執行

ThreadB被喚醒後,從原阻塞處繼續向下執行,這個過程和步驟5(ThreadA喚醒後繼續執行)徹底同樣。

setHeadAndPropagate方法把ThreadB結點變爲頭結點,並根據傳播狀態判斷是否要喚醒並釋放後繼結點:
clipboard.png

①將ThreadB變成頭結點
clipboard.png

②調用doReleaseShared方法,釋放並喚醒後繼結點(此時沒有後繼結點了,則直接break):
clipboard.png

最終隊列狀態以下:
clipboard.png

3、總結

AQS的共享功能,經過鉤子方法tryAcquireShared暴露,與獨佔功能最主要的區別就是:

共享功能的結點,一旦被喚醒,會向隊列後部傳播(Propagate)狀態,以實現共享結點的連續喚醒。這也是共享的含義,當鎖被釋放時,全部持有該鎖的共享線程都會被喚醒,並從等待隊列移除。

相關文章
相關標籤/搜索