摘要:好了,繼續向下執行,嘗試獲取鎖失敗后,會調用首先通過方法,將包裝成共享結點,插入等待隊列,插入完成后隊列結構如下然后會進入自旋操作,先嘗試獲取一次鎖,顯然此時是獲取失敗的主線程還未調用,同步狀態還是。
本文首發于一世流云的專欄:https://segmentfault.com/blog...一、本章概述
AQS系列的前三個章節,我們通過ReentrantLock的示例,分析了AQS的獨占功能。
本章將以CountDownLatch為例,分析AQS的共享功能。CountDownLatch,是J.U.C中的一個同步器類,可作為倒數計數器使用,關于CountDownLatch的使用和說明,讀者可以參考:
Java多線程進階(十八)—— J.U.C之synchronizer框架:CountDownLatch。
CountDownLatch示例
假設現在有3個線程,ThreadA、ThreadB、mainThread,CountDownLatch初始計數為1:
CountDownLatch switcher = new CountDownLatch(1);
線程的調用時序如下:
//ThreadA調用await()方法等待 //ThreadB調用await()方法等待 //主線程main調用countDown()放行二、AQS共享功能的原理 1. 創建CountDownLatch
CountDownLatch的創建沒什么特殊,調用唯一的構造器,傳入一個初始計數值,內部實例化一個AQS子類:
CountDownLatch switcher = new CountDownLatch(1);
可以看到,初始計數值count其實就是同步狀態值,在CountDownLatch中,同步狀態State表示CountDownLatch的計數器的初始大小。
2. ThreadA調用await()方法等待CountDownLatch的await方法是響應中斷的,該方法其實是調用了AQS的acquireSharedInterruptibly方法:
注意tryAcquireShared方法,該方法嘗試獲取鎖,由AQS子類實現,其返回值的含義如下:
State | 資源的定義 |
---|---|
小于0 | 表示獲取失敗 |
0 | 表示獲取成功 |
大于0 | 表示獲取成功,且后繼爭用線程可能成功 |
CountDownLatch中的tryAcquireShared實現相當簡單,當State值為0時,永遠返回成功:
我們之前說了在CountDownLatch中,同步狀態State表示CountDownLatch的計數器的初始值,當State==0時,表示無鎖狀態,且一旦State變為0,就永遠處于無鎖狀態了,此時所有線程在await上等待的線程都可以繼續執行。
而在ReentrantLock中,State==0時,雖然也表示無鎖狀態,但是只有一個線程可以重置State的值。這就是共享鎖的含義。
好了,繼續向下執行,ThreadA嘗試獲取鎖失敗后,會調用doAcquireSharedInterruptibly:
首先通過addWaiter方法,將ThreadA包裝成共享結點,插入等待隊列,插入完成后隊列結構如下:
然后會進入自旋操作,先嘗試獲取一次鎖,顯然此時是獲取失敗的(主線程main還未調用countDown,同步狀態State還是1)。
然后判斷是否要進入阻塞(shouldParkAfterFailedAcquire):
好了,至此,ThreadA進入阻塞態,最終隊列結構如下:
流程和步驟2完全相同,調用后ThreadB也被加入到等待隊列中:
ThreadA和ThreadB調用了await()方法后都在等待了,現在主線程main開始調用countDown()方法,該方法調用后,ThreadA和ThreadB都會被喚醒,并繼續往下執行,達到類似門栓的作用。
來看下countDown方法的內部:
該方法內部調用了AQS的releaseShared方法,先嘗試一次釋放鎖,tryReleaseShared方法是一個鉤子方法,由CountDownLatch實現,當同步State狀態值首次變為0時,會返回true:
先調用compareAndSetWaitStatus將頭結點的等待狀態置為0,表示將喚醒后續結點(ThreadA),成功后的等待隊列結構如下:
然后調用unparkSuccessor喚醒后繼結點(ThreadA被喚醒后會從原阻塞處繼續往下執行,這個在步驟5再講):
此時,等待隊列結構如下:
ThreadA被喚醒后,會從原來的阻塞處繼續向下執行:
由于是一個自旋操作,ThreadA會再次嘗試獲取鎖,由于此時State同步狀態值為0(無鎖狀態),所以獲取成功。然后調用setHeadAndPropagate方法:
setHeadAndPropagate方法把ThreadA結點變為頭結點,并根據傳播狀態判斷是否要喚醒并釋放后繼結點:
①將ThreadA變成頭結點
②調用doReleaseShared方法,釋放并喚醒ThreadB結點
ThreadB被喚醒后,從原阻塞處繼續向下執行,這個過程和步驟5(ThreadA喚醒后繼續執行)完全一樣。
setHeadAndPropagate方法把ThreadB結點變為頭結點,并根據傳播狀態判斷是否要喚醒并釋放后繼結點:
①將ThreadB變成頭結點
②調用doReleaseShared方法,釋放并喚醒后繼結點(此時沒有后繼結點了,則直接break):
最終隊列狀態如下:
AQS的共享功能,通過鉤子方法tryAcquireShared暴露,與獨占功能最主要的區別就是:
共享功能的結點,一旦被喚醒,會向隊列后部傳播(Propagate)狀態,以實現共享結點的連續喚醒。這也是共享的含義,當鎖被釋放時,所有持有該鎖的共享線程都會被喚醒,并從等待隊列移除。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76551.html
摘要:開始獲取鎖終于輪到出場了,的調用過程和完全一樣,同樣拿不到鎖,然后加入到等待隊列隊尾然后,在阻塞前需要把前驅結點的狀態置為,以確保將來可以被喚醒至此,的執行也暫告一段落了安心得在等待隊列中睡覺。 showImg(https://segmentfault.com/img/remote/1460000016012467); 本文首發于一世流云的專欄:https://segmentfault...
摘要:線程可以調用的方法進入阻塞,當計數值降到時,所有之前調用阻塞的線程都會釋放。注意的初始計數值一旦降到,無法重置。 showImg(https://segmentfault.com/img/remote/1460000016012041); 本文首發于一世流云的專欄:https://segmentfault.com/blog... 一、CountDownLatch簡介 CountDow...
摘要:公平策略在多個線程爭用鎖的情況下,公平策略傾向于將訪問權授予等待時間最長的線程。使用方式的典型調用方式如下二類原理的源碼非常簡單,它通過內部類實現了框架,接口的實現僅僅是對的的簡單封裝,參見原理多線程進階七鎖框架獨占功能剖析 showImg(https://segmentfault.com/img/remote/1460000016012582); 本文首發于一世流云的專欄:https...
摘要:關于,最后有兩點規律需要注意當的等待隊列隊首結點是共享結點,說明當前寫鎖被占用,當寫鎖釋放時,會以傳播的方式喚醒頭結點之后緊鄰的各個共享結點。當的等待隊列隊首結點是獨占結點,說明當前讀鎖被使用,當讀鎖釋放歸零后,會喚醒隊首的獨占結點。 showImg(https://segmentfault.com/img/remote/1460000016012293); 本文首發于一世流云的專欄:...
摘要:整個包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據一系列常見的多線程設計模式,設計了并發包,其中包下提供了一系列基礎的鎖工具,用以對等進行補充增強。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發于一世流云專欄:https...
閱讀 1646·2023-04-26 02:11
閱讀 2990·2023-04-25 16:18
閱讀 3720·2021-09-06 15:00
閱讀 2636·2019-08-30 15:55
閱讀 1942·2019-08-30 13:20
閱讀 2058·2019-08-26 18:36
閱讀 3131·2019-08-26 11:40
閱讀 2549·2019-08-26 10:11