一點閑扯
開始說AQS之前圃伶,繼續(xù)說上一篇沒說完的建議悲关,相對于看一些不知道時效性的blog襟锐,說實話撤逢,理解一個知識點最簡便的方式就是看論文及源碼實現(xiàn)了,解決一個問題最好的方式就是看官方文檔及源碼粮坞,沒有什么答案是在源碼里找不到的蚊荣,對著blog排查問題只會導(dǎo)致抱著一個黑盒子在原地踏步,偶爾運氣好時間短過去了莫杈,運氣不好互例,花很長時間不說,下次問題稍微變動一下就又不會了筝闹。切身體會媳叨,關(guān)于Concurrent的論文,The java.util.concurrent Synchronizer Framewor丁存,大家如果有需要可以私信肩杈。
AQS 原理
AQS維護(hù)了一個volatile int state(可以理解為鎖狀態(tài))和一個FIFO線程等待隊列(多線程爭用資源被阻塞時會進(jìn)入此隊列)。
放一張示意圖:
AQS的設(shè)計使用了一個解決爭用問題的經(jīng)典經(jīng)常解寝,有的稱為
驚群效應(yīng)
扩然,在AQS實現(xiàn)思路之前:如果存在n個(數(shù)量非常多)的線程等待競爭鎖,當(dāng)鎖釋放時會喚醒所有線程去競爭鎖聋伦,但最后肯定僅有一個鎖競爭成功夫偶。其他線程只能再次等待喚醒再次競爭,這導(dǎo)致資源的大量浪費觉增。所以我們需要一種策略來喚醒某個特定的線程避免過多的喚醒及響應(yīng)爭用時的資源浪費兵拢。AQS維護(hù)了一個FIFO的隊列,每個節(jié)點僅關(guān)心前一個節(jié)點的狀態(tài)逾礁,喚醒也僅僅是喚醒首節(jié)點说铃。這種實現(xiàn)方式不僅僅是AQS,更經(jīng)典的是Zookeeper中分布式鎖的實現(xiàn)嘹履。
AQS定義兩種資源共享方式:Exclusive
(獨占腻扇,只有一個線程能執(zhí)行,如ReentrantLock
)和Share
(共享砾嫉,多個線程可同時執(zhí)行幼苛,如Semaphore/CountDownLatch
)。不同的自定義同步器爭用共享資源的方式也不同焕刮。自定義同步器在實現(xiàn)時只需要實現(xiàn)共享資源state的獲取與釋放方式即可舶沿,至于具體線程等待隊列的維護(hù)(如獲取資源失敗入隊/喚醒出隊等)墙杯,AQS已經(jīng)在頂層實現(xiàn)好了。
AQS 源碼實現(xiàn)
AQS 全稱AbstractQueuedSynchronizer括荡,是CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基礎(chǔ)高镐,因此AbstractQueuedSynchronizer是Lock/Executor實現(xiàn)的前提,然后因為AQS是一個抽象類一汽,如若想要使用AQS繼承就可避消,不過日常場景中大多是使用它的上層應(yīng)用。還有另一個類叫做AbstractQueuedLongSynchronizer召夹,這是不知道為什么之前看過的所有blog都沒有提到岩喷,看了下官方文檔這個是Java 8之后的實現(xiàn),一個64位版本监憎。其結(jié)構(gòu)和功能與AbstractQueuedSynchronizer一致纱意。
下面開始看代碼:
/**
* @since 1.6
* @author Doug Lea
*/
public abstract class AbstractQueuedLongSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414692L;
它實現(xiàn)了序列化接口,然后繼承自AbstractOwnableSynchronizer
鲸阔。
AbstractOwnableSynchronizer
獨占線程 為transient的偷霉,private transient Thread exclusiveOwnerThread,然后是獨占線程的setter和getter方法褐筛,就是將獨占線程的使用抽離出AQS的使用中类少。
下面看AQS持有的對象成員,我把注釋翻譯一下渔扎,可能就很清晰了硫狞,如果不正確,肯請指正晃痴,我能及時修改残吩。
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
*等待隊列的頭部,延遲初始化倘核。除了初始化泣侮,僅通過方法setHead進(jìn)行修改。
*注意:如果head存在紧唱,則保證不存在其等待狀態(tài)活尊。
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*等待隊列的尾部,延遲初始化漏益。僅通過方法enq修改以添加新的等待節(jié)點酬凳。
*/
private transient volatile Node tail;
/**
* The synchronization state.
*具體的同步狀態(tài)
*/
private volatile long state;
然后下面幾個函數(shù)是這幾個成員的setter、getter遭庶,其中Set、compareAndSet等函數(shù)與Java 8的實現(xiàn)不同的是稠屠,使用VarHandle實現(xiàn)峦睡,具體VarHandle介紹可以參考上一篇《Java Concurrent Atomic(JDK 10)》翎苫。
接下來是一個自旋為超時閾值。
/**
* The number of nanoseconds for which it is faster to spin
* rather than to use timed park. A rough estimate suffices
* to improve responsiveness with very short timeouts.
*實際能更快地旋轉(zhuǎn)而不是使用固定的納秒數(shù)榨了。粗略的估計足以在很短的時間內(nèi)提高響應(yīng)能力煎谍。
*/
static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L;
函數(shù)內(nèi)容都是雙指針鏈表的經(jīng)典操作,但是代碼寫的很優(yōu)雅龙屉,參考下代碼規(guī)范也是很不錯的呐粘。
以下為基礎(chǔ)的支撐函數(shù):
enq
:insert函數(shù)
addwaiter
:創(chuàng)建要添加的node
unparkSuccessor
:如果存在元素,彈出元素转捕。
到重要部分了:很明顯的看到里面使用的經(jīng)典的CAS操作作岖,具體的細(xì)節(jié)可以看下面代碼。
作用:釋放共享模式的操作——信號繼承并確保傳播五芝。(注意:對于獨占模式痘儡,釋放只是在需要信號時調(diào)用head的unpark繼承者)。屬于通過CAS對于信號量進(jìn)行正確性控制枢步。
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
(0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
下面是public函數(shù)的內(nèi)部支持函數(shù)沉删,細(xì)節(jié)不過多描述,簡述功能醉途,具體實現(xiàn)太長了矾瑰,大家可以參照以下看對應(yīng)的源碼。
setHeadAndPropagate
:設(shè)置隊列頭部隘擎,并檢查后續(xù)是否在共享模式下等待殴穴,如果傳播了> 0或傳播狀態(tài),則進(jìn)行傳播嵌屎。
cancelAcquire
:取消正在嘗試的取操作推正。
shouldParkAfterFailedAcquire
:檢查和更新未能獲取的節(jié)點的狀態(tài)。如果線程應(yīng)該阻塞宝惰,返回true植榕。這是所有獲取循環(huán)的主要信號控制。要求pred = node.prev
尼夺。
selfInterrupt
:中斷當(dāng)前線程
parkAndCheckInterrupt
:檢查是否處于被中斷狀態(tài)
acquireQueued
:為隊列中已經(jīng)存在的線程獲取獨占的不可中斷模式尊残。用于條件等待方法和獲取方法。
doAcquireInterruptibly
:獲取獨占可中斷模式淤堵。
展示一點細(xì)節(jié)性的東西寝衫,對咱們寫代碼很有幫助的。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return true;
}
然后是各種模式下的取操作拐邪,不一一簡述了慰毅。
fullGetFirstQueuedThread
:當(dāng)fastpath失敗時調(diào)用的getFirstQueuedThread的版本。
對外開放的使用函數(shù):
hasQueuedThreads
:展示持有的線程
hasContended
:查詢是否有任何線程爭取這個同步器;也就是說扎阶,如果獲取方法曾經(jīng)被阻塞汹胃。
getFirstQueuedThread
:返回隊首元素婶芭,若果沒有則null。
isQueued
:檢查是否在排隊
apparentlyFirstQueuedIsExclusive
:返回{@code true}着饥,如果第一個明顯的隊列線程(如果存在)正在排他模式中等待犀农。如果該方法返回{@code true},并且當(dāng)前線程正在嘗試以共享模式獲取(即宰掉,該方法從{@link)調(diào)用然后保證當(dāng)前線程不是第一個排隊的線程呵哨。僅用于ReentrantReadWriteLock
中的啟發(fā)式。
hasQueuedPredecessors
:查詢是否有任何線程等待獲取的時間超過當(dāng)前線程轨奄。
getQueueLength
:獲取長度
getQueuedThreads
:獲取正在等待的線程
getExclusiveQueuedThreads
:返回一個包含可能等待以獨占模式獲取的線程的集合孟害。它具有與{@link #getQueuedThreads}相同的屬性,但是它只返回由于獨占獲取而等待的線程戚绕。
getExclusiveQueuedThreads
:功能同上纹坐,但是這個獲取的是shared模式下的
isOnSyncQueue
:如果一個節(jié)點(總是最初放在條件隊列上的節(jié)點)現(xiàn)在正在等待同步隊列上的重新獲取,則返回true舞丛。
findNodeFromTail
:如果節(jié)點通過向后搜索耘子,則返回true。只有在isOnSyncQueue需要時才調(diào)用球切。
transferForSignal
:將一個節(jié)點從條件隊列傳輸?shù)酵疥犃泄仁摹H绻晒Γ祷豻rue吨凑。
owns
:查詢給定的ConditionObject是否使用這個同步器作為其鎖捍歪。
hasWaiters
:查詢是否有任何線程正在等待與此同步器相關(guān)的給定條件。注意鸵钝,由于超時和中斷可能在任何時候發(fā)生糙臼,{@code true}返回不能保證將來的{@code signal}將喚醒任何線程。該方法主要用于監(jiān)控系統(tǒng)狀態(tài)恩商。
getWaitQueueLength
:返回與此同步器相關(guān)聯(lián)的給定條件上等待的線程數(shù)的估計值变逃。注意,由于超時和中斷可能在任何時候發(fā)生怠堪,因此估計僅作為實際服務(wù)員數(shù)量的上限揽乱。這種方法設(shè)計用于監(jiān)視系統(tǒng)狀態(tài),而不是用于同步控制粟矿。
以下幾個都是不能保證完全正確的估略值凰棉。
然后最后是一個內(nèi)部類,針對信號量及節(jié)點的條件操作陌粹∪鱿可以學(xué)習(xí)這種方式,將特定的規(guī)則收斂到對應(yīng)的結(jié)構(gòu)。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
差不多就這樣或舞,過程可能有些枯燥隧膏,以上介紹了大約90%以上的函數(shù),及關(guān)鍵操作嚷那,正如一開始說的,要想深入理解杆煞,還需要對著文章去看源碼魏宽。
AQS除了基本的信號操作及隊列操作,依賴的實現(xiàn)就是volatile及對應(yīng)的CAS操作决乎,而CAS是有VarHandle所支持队询,VarHandle又依賴于native函數(shù),JNI本地函數(shù)調(diào)用构诚。
說完AQS再加上之前的Atomic蚌斩,第二層結(jié)構(gòu)已經(jīng)清晰了。
下面開始說第三層范嘱,也是代碼量及工具量非常龐大的一層送膳,后續(xù)可能更加枯燥,除去基礎(chǔ)使用的API丑蛤,更愿意簡述里面的實現(xiàn)原理叠聋。