Java并發(fā)源碼剖析(二)——AbstractQueuedSynchronizer共享模式

圖片來源網(wǎng)絡(luò).png

0抡诞、引言

作為并發(fā)相關(guān)內(nèi)容的第二篇洽瞬,這里主要延續(xù)上一篇文章《Java并發(fā)源碼剖析(一)——AbstractQueuedSynchronizer獨(dú)占模式》的內(nèi)容繼續(xù)介紹共享模式的知識相叁。

1敞映、共享式AQS

獨(dú)占式AQS主要是運(yùn)用在ReentrantLock內(nèi)朱嘴,而共享式AQS主要是Semaphore返奉、CountDownLatch变逃。它旨在提供一個可以同時通過多個線程的阻塞方式必逆。獨(dú)占式是一種悲觀鎖,而共享式是一種樂觀鎖揽乱。這也是它與獨(dú)占式AQS最大的區(qū)別名眉。

2、共享式的機(jī)制

2.1凰棉、acquire共享模式

共享式的acquire操作與獨(dú)占式的差別不太大损拢,也是調(diào)用子類的tryAcquireShared方法,如果失敗后才進(jìn)行后續(xù)的操作撒犀。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

2.1.1福压、doAcquireShared

總的來說,兩者的代碼思路是一致的或舞,我將兩個代碼放在一起供大家做一個對比荆姆。如果有不是很了解的可以參考我的第一篇文章。

acquire函數(shù)的代碼對比圖.png

這里主要指出一下不同映凳。

  1. addWaiter設(shè)置為shared模式胆筒。
  2. tryAcquire和tryAcquireShared的返回值不同,因此會多出一個判斷過程
  3. 在判斷前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)后诈豌,調(diào)用了setHeadAndPropagate方法腐泻,而不是簡單的更新一下頭節(jié)點(diǎn)决乎。(如圖中紅線標(biāo)出的地方)

2.1.2、setHeadAndPropagate

設(shè)置頭節(jié)點(diǎn)狀態(tài)派桩,并通過propagate判斷是否可以允許acquire

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; 
    setHead(node);
    // propagate也就是state的更新值大于0构诚,代表可以繼續(xù)acquire
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 判斷后繼節(jié)點(diǎn)是否存在,如果存在是否是共享模式的節(jié)點(diǎn)
        // 然后進(jìn)行共享模式的釋放
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

在這里h頭節(jié)點(diǎn)進(jìn)行了兩次判定铆惑,第一次是判定舊頭節(jié)點(diǎn)存在且狀態(tài)已經(jīng)被設(shè)置過范嘱,第二次是判定設(shè)置后的頭節(jié)點(diǎn)是否存在并且狀態(tài)已經(jīng)被設(shè)置過。只有滿足上述的一個條件员魏,就會對其后繼節(jié)點(diǎn)做判斷丑蛤。后繼節(jié)點(diǎn)不存在或者后繼節(jié)點(diǎn)是共享模式,那就可以對整個隊列進(jìn)行釋放操作撕阎。

Q1:這里為什么要判斷狀態(tài)為小于0的情況受裹,直接使用PROPAGATE狀態(tài)不可以嗎?
這個是因為PROPAGATE狀態(tài)是會被轉(zhuǎn)換為SIGNAL的虏束,這個在shouldParkAfterFailedAcquire方法中處理的棉饶,不清楚的可以再翻閱一下之前的文章。

這里有個小疑問镇匀,我也沒太想明白照藻??汗侵?

為什么要去對老節(jié)點(diǎn)的狀態(tài)去做一個判斷幸缕,源碼中注釋解釋的是在多重acquire/release情況下會出現(xiàn)不必要的喚醒。反正我是沒懂這是什么意思晰韵?

2.1.3发乔、doReleaseShared

頭節(jié)點(diǎn)開始, 判斷頭節(jié)點(diǎn)后繼的狀態(tài)雪猪,來確定后繼需不需要喚醒列疗。

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        // 只需要處理頭節(jié)點(diǎn)和尾節(jié)點(diǎn)都存在,且隊列內(nèi)的節(jié)點(diǎn)總數(shù)超過1個的情況
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 兩種模式下都需要SIGNAL信號來判斷是否喚醒后繼節(jié)點(diǎn)
            if (ws == Node.SIGNAL) {
                // 如果CAS操作失敗了就繼續(xù)循環(huán)處理
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
                    continue;
                }
                // CAS操作成功后浪蹂,就將后繼節(jié)點(diǎn)解除阻塞
                unparkSuccessor(h);
            } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
                continue;
            }
            // 當(dāng)狀態(tài)碼是PROPAGATE的時候抵栈,就可以結(jié)束循環(huán)了
        }
        // 在循環(huán)過程中,為了防止在上述操作過程中新添加了節(jié)點(diǎn)的情況坤次,
        // 通過檢查頭節(jié)點(diǎn)是否改變了古劲,如果改變了就繼續(xù)循環(huán)
        if (h == head)
            break;
    }
}

由于共享模式和獨(dú)占模式的隊列是一樣的,他們都需要區(qū)分出SIGNAL信號缰猴,然后對該后繼節(jié)點(diǎn)進(jìn)行阻塞的接觸产艾。對于狀態(tài)為初始值的節(jié)點(diǎn),就可以將其狀態(tài)設(shè)置為PROPAGATE,不做任何其他操作闷堡,結(jié)束循環(huán)隘膘。

這里的循環(huán)主要是檢測是否在上述操作中,有新的節(jié)點(diǎn)加入到隊列中杠览。

2.2弯菊、release共享模式

共享模式下的release操作與獨(dú)占式無太大差別,而核心方法還是在于doReleaseShared踱阿。具體方法內(nèi)容在2.4小節(jié)內(nèi)已經(jīng)解釋過管钳,可以返回到上一小節(jié)再分析一下。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

因為從頭節(jié)點(diǎn)開始的節(jié)點(diǎn)進(jìn)行處理软舌,所以對于共享模式的release來說才漆,一般來說,只需要判斷兩種情況佛点,一種SIGNAL代表后繼節(jié)點(diǎn)之前被阻塞了需要釋放醇滥,而另一種是PROPAGATE代表共享模式下可以繼續(xù)進(jìn)行acquire。

3超营、小結(jié)

共享式的操作與獨(dú)占式的主要區(qū)別在于鸳玩,每次acquire競爭失敗后,獨(dú)占式將立即阻塞當(dāng)前線程糟描,而共享式需要在多次acquire失敗后才會阻塞當(dāng)前線程怀喉。簡單來說书妻,共享式是有一定限額的獨(dú)占式船响。限額的滿足方式,根據(jù)不同的子類不同的實現(xiàn)方式躲履。

參考文章

  1. Java并發(fā)-獨(dú)占鎖與共享鎖
  2. Java并發(fā)包源碼學(xué)習(xí)之AQS框架(四)AbstractQueuedSynchronizer源碼分析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末见间,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子工猜,更是在濱河造成了極大的恐慌米诉,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篷帅,死亡現(xiàn)場離奇詭異史侣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)魏身,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門惊橱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人箭昵,你說我怎么就攤上這事税朴。” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵正林,是天一觀的道長泡一。 經(jīng)常有香客問我,道長觅廓,這世上最難降的妖魔是什么鼻忠? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哪亿,結(jié)果婚禮上粥烁,老公的妹妹穿的比我還像新娘。我一直安慰自己蝇棉,他們只是感情好讨阻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著篡殷,像睡著了一般钝吮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上板辽,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天奇瘦,我揣著相機(jī)與錄音,去河邊找鬼劲弦。 笑死耳标,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的邑跪。 我是一名探鬼主播次坡,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼画畅!你這毒婦竟也來了砸琅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤轴踱,失蹤者是張志新(化名)和其女友劉穎症脂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淫僻,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诱篷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了雳灵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棕所。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖细办,靈堂內(nèi)的尸體忽然破棺而出橙凳,到底是詐尸還是另有隱情蕾殴,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布岛啸,位于F島的核電站钓觉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏坚踩。R本人自食惡果不足惜荡灾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞬铸。 院中可真熱鬧批幌,春花似錦、人聲如沸嗓节。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拦宣。三九已至截粗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸵隧,已是汗流浹背绸罗。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豆瘫,地道東北人珊蟀。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像外驱,于是被迫代替她去往敵國和親育灸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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