J.U.C之AQS:源碼解析:共享式同步

獲取共享式同步狀態(tài)

入口函數(shù)

     /**
     * 共享式獲取同步狀態(tài),如果當(dāng)前線程獲取同步狀態(tài)成功則直接返回,
     * 如果獲取失敗則線程阻塞络凿,并插入同步隊(duì)列進(jìn)行.等待調(diào)用releaseShared
     * 釋放同步狀態(tài)時(shí),重新嘗試獲取同步狀態(tài)虱朵。成功則,同時(shí)會(huì)通知后置節(jié)點(diǎn)線程從阻塞中喚醒,
     * 獲取同步狀態(tài)并返回吧兔,失敗則阻塞等待下次release
     */
    public final void acquireShared(int arg) {
        /**
         *子類實(shí)現(xiàn)tryAcquireShared能否獲取的共享式同步狀態(tài)
         *如果返回>=0則獲取同步狀態(tài)成功方法直接返回
         *如果返回< 0則獲取同步狀態(tài)失敗進(jìn)入if語(yǔ)句
         */
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

doAcquireShared

  • 1 創(chuàng)建節(jié)點(diǎn)node(類型是共享式),添加到同步隊(duì)列尾部,默認(rèn)狀態(tài)為0 【同獨(dú)占式同步相同】
  • 2 進(jìn)入自旋【同獨(dú)占式同步相同】
  • 3 判斷當(dāng)前節(jié)點(diǎn)前置節(jié)點(diǎn)是否為head節(jié)點(diǎn),如果是重新嘗試獲取同步狀態(tài)【同獨(dú)占式同步相同】
  • 4 如果同步狀態(tài)成功,設(shè)置當(dāng)前節(jié)點(diǎn)為head節(jié)點(diǎn).會(huì)判斷同步隊(duì)列中是否存在等待的共享節(jié)點(diǎn).如果存在則會(huì)調(diào)用doReleaseShared喚醒新head節(jié)點(diǎn)后置節(jié)點(diǎn)阻塞,被喚醒的節(jié)點(diǎn)線程重新進(jìn)入自旋袍嬉。【同獨(dú)占式同步不相同】
  • 5 嘗試獲取同步狀態(tài)失敗,獲取同步狀態(tài)失敗,就將當(dāng)前節(jié)點(diǎn)和前驅(qū)節(jié)點(diǎn)作為參數(shù)交給shouldParkAfterFailedAcquire調(diào)用灶平,shouldParkAfterFailedAcquire設(shè)置要當(dāng)前節(jié)點(diǎn)前驅(qū)節(jié)點(diǎn)node等待狀態(tài)到-1返回false伺通,自旋第二次進(jìn)入時(shí)返回true,到parkAndCheckInterrupt()阻塞當(dāng)前線程》晗恚【同獨(dú)占式同步相同】
  • 6 如果節(jié)點(diǎn)線程被從阻塞中喚醒重新進(jìn)入自旋 【同獨(dú)占式同步相同】

這里步驟4 表示了和獨(dú)占最大的不同:獨(dú)占同步的節(jié)點(diǎn)獲得同步狀態(tài)后罐监,直接退出同步隊(duì)列。而共享同步的節(jié)點(diǎn)會(huì)通知自己后置的同步節(jié)點(diǎn)線程(后置也是共享節(jié)點(diǎn))從阻塞中喚醒去嘗試獲取同步狀態(tài)瞒爬,如果獲取同步狀態(tài)成功弓柱,則表示2個(gè)線程獲得了一把鎖(一次釋放動(dòng)作表示一把鎖),同時(shí)執(zhí)行侧但。同時(shí)第2個(gè)線程的節(jié)點(diǎn)同樣會(huì)通知后置同伴節(jié)點(diǎn)線程從阻塞中喚醒并嘗試獲取同步狀態(tài)矢空。

/**
     * 創(chuàng)建一個(gè)共享式節(jié)點(diǎn)node,添加到同步隊(duì)列尾部.
     * 進(jìn)入自旋,找到CLH頭部后置第一個(gè)節(jié)點(diǎn),嘗試獲取同步狀態(tài),成功則設(shè)置其為新head節(jié)點(diǎn)禀横,
     * 并通知后置節(jié)點(diǎn)線程從阻塞中喚醒競(jìng)爭(zhēng)同步狀態(tài).失敗則阻塞.
     */
    private void doAcquireShared(int arg) {
        /** 創(chuàng)建一個(gè)共享式節(jié)點(diǎn)node,添加到同步隊(duì)列尾部..**/
        final Node node = addWaiter(Node.SHARED);
        /** 執(zhí)行是否發(fā)生異常 **/
        boolean failed = true;
        try {
            /** 標(biāo)識(shí)是否被中斷 **/
            boolean interrupted = false;
            /** 進(jìn)入自旋 **/
            for (;;) {
                /** 1. 獲得當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)  **/
                final Node p = node.predecessor();
                if (p == head) {
                    /** 如果當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)并且成功獲取同步狀態(tài) **/
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        /** 將當(dāng)前節(jié)點(diǎn)設(shè)置為head,同時(shí)只要同步隊(duì)列中存在等待的節(jié)點(diǎn),
                         * 且節(jié)點(diǎn)為共享節(jié)點(diǎn)則喚醒head節(jié)點(diǎn)后置節(jié)點(diǎn)阻塞去競(jìng)爭(zhēng)同步狀態(tài). **/
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        /** 如果當(dāng)前線程中斷 **/
                        if (interrupted)
                            /** 中斷當(dāng)前線程 **/
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                /** 獲取鎖失敗屁药,在shouldParkAfterFailedAcquire中設(shè)置節(jié)點(diǎn)的等待狀態(tài),并線程阻塞(可響應(yīng)線程被中斷),
                 * 如果是中斷響應(yīng)設(shè)置interrupted = true;
                 * 重新進(jìn)入自旋**/
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            /** 發(fā)生異常柏锄,將當(dāng)前節(jié)點(diǎn)等待狀態(tài)設(shè)置為取消**/
            if (failed)
                cancelAcquire(node);
        }
    }

setHeadAndPropagate

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);

        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

doReleaseShared

1 內(nèi)部通過(guò)一個(gè)自旋酿箭,找到Head節(jié)點(diǎn)后置節(jié)點(diǎn)中線程從阻塞狀態(tài)中喚醒競(jìng)爭(zhēng)同步狀態(tài).

2 同時(shí)會(huì)檢測(cè)釋放節(jié)點(diǎn)的線程是否會(huì)獲取同步狀態(tài),如果檢查獲取同步狀態(tài)成功趾娃,則在次釋放節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)中的線程從阻塞中喚醒缭嫡,重復(fù)迭代(是多線程可能釋放的線程還沒(méi)來(lái)的及獲取同步狀態(tài)。同步隊(duì)列head節(jié)點(diǎn)沒(méi)有改變退出的情況也是存在)因而此出邏輯作用是為性能考慮抬闷。

3 即使沒(méi)有檢測(cè)釋放釋放線程獲取到同步狀態(tài)返回妇蛀,也不會(huì)太大影響,因?yàn)槊總€(gè)線程獲取同步狀態(tài)時(shí)還是會(huì)調(diào)用setHeadAndPropagate檢查是否需要釋放自己下個(gè)節(jié)點(diǎn)的線程的阻塞饶氏。

private void doReleaseShared() {
        //進(jìn)入自旋
        for (;;) {
            //獲取當(dāng)前head節(jié)點(diǎn),判斷等待隊(duì)列中是否存在等待節(jié)點(diǎn),不存在進(jìn)入步驟4直接退出.
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //如果當(dāng)前節(jié)點(diǎn)的狀態(tài)為-1(存在等待的后置節(jié)點(diǎn)),使用CAS設(shè)置其為0(使用CAS失敗進(jìn)入自旋重新設(shè)置)同時(shí)會(huì)喚醒head后置節(jié)點(diǎn)中線程從阻塞中釋放.
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                //如果當(dāng)前節(jié)點(diǎn)的狀態(tài)為0(存在后置節(jié)點(diǎn)已經(jīng)取消等待),使用CAS設(shè)置其為PROPAGATE(使用CAS失敗進(jìn)入自旋重新設(shè)置),
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            /**
            判斷喚醒線程是否獲取同步狀態(tài),獲取同步狀態(tài)同步隊(duì)列head節(jié)點(diǎn)引用會(huì)發(fā)生改變讥耗,則釋放被釋放節(jié)點(diǎn)后置節(jié)點(diǎn)的線程中阻塞中喚醒競(jìng)爭(zhēng)同步狀態(tài)
            **/
            if (h == head)                   // loop if head changed
                break;
        }
    }

釋放共享式同步狀態(tài)

入口函數(shù)

releaseShared

public final boolean releaseShared(int arg) {
         /*
        *子類實(shí)現(xiàn)能否釋放的共享式同步狀態(tài)
        *如果返回true則表示釋放同步狀態(tài)準(zhǔn)入條件成功進(jìn)入if語(yǔ)句
        *如果返回false則表示釋放同步狀態(tài)失敗返回false
        */
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

總結(jié)

獨(dú)占式同步可以看作是一把共享鎖,每次可以多個(gè)線程獲的鎖疹启。

總體流程如下

嘗試鎖失敗 --> 進(jìn)入等待隊(duì)列排隊(duì)  -->  阻塞當(dāng)前線程 --> 當(dāng)?shù)却?duì)列排到自己被喚醒 --> 嘗試獲取鎖(可能被其他線程插隊(duì)而導(dǎo)致獲取鎖失敗古程,失敗在次阻塞,等待下次排到自己)--> 成功通知等待隊(duì)列前面共享節(jié)點(diǎn)線程從阻塞中喚醒  --> 執(zhí)行自己的業(yè)務(wù)邏輯 --> 嘗試釋放鎖--> 成功通知等待隊(duì)列前面共享節(jié)點(diǎn)線程從阻塞中喚醒喊崖。

子類可以擴(kuò)展如何獲取鎖挣磨,如何釋放鎖雇逞,但整體流程不變。

白話

我們舉一個(gè)生活中例子來(lái)看獨(dú)占式同步茁裙,比如我們?nèi)ゲ蛷d吃過(guò)飯塘砸,餐廳吃飯這個(gè)動(dòng)作就是我們要執(zhí)行業(yè)務(wù),但要進(jìn)入餐廳吃飯的前提要獲取同步狀態(tài)晤锥。對(duì)于獨(dú)占來(lái)說(shuō)只有一個(gè)位置掉蔬,那么就獨(dú)占式同步,那么進(jìn)入餐廳這個(gè)動(dòng)作(獲取鎖)只能保證同時(shí)一個(gè)人吃飯(一個(gè)線程)矾瘾,離開(kāi)餐廳這個(gè)動(dòng)作(釋放鎖)也只能同時(shí)保證一個(gè)進(jìn)入(一個(gè)線程)女轿。那么對(duì)于共享式同步,餐廳多個(gè)位置壕翩,那么就是共享式同步蛉迹,因?yàn)橥蕉鄠€(gè)人進(jìn)入餐廳吃飯。當(dāng)然能不能進(jìn)入邏輯時(shí)餐廳這個(gè)AQS子類去定制的放妈。

我們來(lái)看共享式例子北救,此時(shí)餐廳共有2個(gè)位置。

客人A進(jìn)入餐廳吃飯芜抒,餐廳此時(shí)沒(méi)有人珍策,他可以順利進(jìn)入餐廳吃飯。

客人B進(jìn)入餐廳吃飯挽绩,餐廳還有位置膛壹,他可以順利進(jìn)入餐廳吃飯。

客戶C也進(jìn)入餐廳吃飯唉堪,餐廳此時(shí)被A占著模聋,嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷),進(jìn)入餐廳失敗

餐廳給C指定一個(gè)排隊(duì)編號(hào)唠亚,此時(shí)C想插隊(duì)链方,在次嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷),A,B還沒(méi)有出來(lái)灶搜,進(jìn)入餐廳失敗阻塞等待

客戶D也進(jìn)入餐廳吃飯祟蚀,餐廳此時(shí)被D占著,嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷)割卖,進(jìn)入餐廳失敗

餐廳給D指定一個(gè)排隊(duì)編號(hào)前酿,此時(shí)D想插隊(duì),在次嘗試進(jìn)入餐廳(子類實(shí)現(xiàn)邏輯判斷)鹏溯,A,B還沒(méi)有出來(lái)罢维,進(jìn)入餐廳失敗阻塞等待

A 吃飯完畢離開(kāi)餐廳,嘗試離開(kāi)餐廳(子類實(shí)現(xiàn)邏輯判斷)丙挽,成功餐廳通知C(喚醒C線程)肺孵,C嘗試進(jìn)入餐廳(可能還會(huì)被插隊(duì))匀借,成功,餐廳通知D(喚醒D線程),
D嘗試進(jìn)入餐廳平窘,B還沒(méi)有出來(lái)吓肋,進(jìn)入餐廳失敗阻塞等待

B 吃飯完畢離開(kāi)餐廳,嘗試離開(kāi)餐廳(子類實(shí)現(xiàn)邏輯判斷)瑰艘,成功餐廳通知D(喚醒D線程),D嘗試進(jìn)入餐廳是鬼,成功

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市紫新,隨后出現(xiàn)的幾起案子屑咳,更是在濱河造成了極大的恐慌,老刑警劉巖弊琴,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異杖爽,居然都是意外死亡敲董,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門慰安,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)腋寨,“玉大人,你說(shuō)我怎么就攤上這事化焕√汛埽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵撒桨,是天一觀的道長(zhǎng)查刻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)凤类,這世上最難降的妖魔是什么穗泵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮谜疤,結(jié)果婚禮上佃延,老公的妹妹穿的比我還像新娘。我一直安慰自己夷磕,他們只是感情好履肃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著坐桩,像睡著了一般尺棋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撕攒,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天陡鹃,我揣著相機(jī)與錄音烘浦,去河邊找鬼。 笑死萍鲸,一個(gè)胖子當(dāng)著我的面吹牛闷叉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脊阴,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼握侧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了嘿期?” 一聲冷哼從身側(cè)響起品擎,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎备徐,沒(méi)想到半個(gè)月后萄传,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜜猾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年秀菱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹭睡。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衍菱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肩豁,到底是詐尸還是另有隱情脊串,我是刑警寧澤,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布清钥,位于F島的核電站琼锋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏祟昭。R本人自食惡果不足惜斩例,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望从橘。 院中可真熱鬧念赶,春花似錦、人聲如沸恰力。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)踩萎。三九已至停局,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背董栽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工码倦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锭碳。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓袁稽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親擒抛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子推汽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

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

  • 2018-05-22 06:15 · 字?jǐn)?shù) 825 · 閱讀 155 · 情緒能量 如果你是對(duì)的,沒(méi)有必要發(fā)脾氣歧沪;...
    SJL四姐閱讀 283評(píng)論 1 0
  • 一抹夕陽(yáng)歹撒,半映著酡紅的臉龐。 無(wú)盡的遐思诊胞,散落在天邊暖夭。從青春到遲暮,生命的歌撵孤,在余暉里飄蕩鳞尔。 ...
    荷塘月色131419閱讀 234評(píng)論 9 10
  • 燈初上霞扬,夜未央。 有一首歌的第一句是它枫振,接下來(lái)是:來(lái)往的人多匆忙喻圃,我不要太緊張,和別人一模樣粪滤。 還知道一個(gè)版本斧拍,后...
    啊永青啊閱讀 357評(píng)論 0 0
  • 看此文時(shí)予权,請(qǐng)當(dāng)身處于2013年12月31日昂勉。 算一算,今年買的書(shū)還真多扫腺。岗照。。大陸版,臺(tái)版攒至,日版加起來(lái)快200本了厚者。...
    白樓孤魂閱讀 865評(píng)論 0 53