Java Concurrent AQS原理&源碼概要(Java 10)

一點閑扯

開始說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)入此隊列)。
放一張示意圖:

image.png

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 (![h.compareAndSetWaitStatus(Node](http://h.compareandsetwaitstatus(node/).SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     ![h.compareAndSetWaitStatus](http://h.compareandsetwaitstatus/)(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)原理叠聋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市受裹,隨后出現(xiàn)的幾起案子碌补,更是在濱河造成了極大的恐慌,老刑警劉巖棉饶,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厦章,死亡現(xiàn)場離奇詭異,居然都是意外死亡照藻,警方通過查閱死者的電腦和手機(jī)袜啃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岩梳,“玉大人囊骤,你說我怎么就攤上這事〖街担” “怎么了也物?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長列疗。 經(jīng)常有香客問我滑蚯,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任告材,我火速辦了婚禮坤次,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斥赋。我一直安慰自己缰猴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布疤剑。 她就那樣靜靜地躺著滑绒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪隘膘。 梳的紋絲不亂的頭發(fā)上疑故,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音弯菊,去河邊找鬼纵势。 笑死,一個胖子當(dāng)著我的面吹牛管钳,可吹牛的內(nèi)容都是我干的钦铁。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼蹋嵌,長吁一口氣:“原來是場噩夢啊……” “哼育瓜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起栽烂,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤躏仇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后腺办,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焰手,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年怀喉,在試婚紗的時候發(fā)現(xiàn)自己被綠了书妻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡躬拢,死狀恐怖躲履,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聊闯,我是刑警寧澤工猜,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站菱蔬,受9級特大地震影響篷帅,放射性物質(zhì)發(fā)生泄漏史侣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一魏身、第九天 我趴在偏房一處隱蔽的房頂上張望惊橱。 院中可真熱鬧,春花似錦箭昵、人聲如沸税朴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掉房。三九已至,卻和暖如春慰丛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘾杭。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工诅病, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粥烁。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓贤笆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讨阻。 傳聞我的和親對象是個殘疾皇子芥永,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容