JUC-(11)AQS(中)-共享模式

對比

獨(dú)占模式 共享模式
acquire(int arg) acquireShared(int arg)
acquireInterruptibly(int arg) acquireSharedInterruptibly(int arg)
tryAcquireNanos(int arg, long nanosTimeout) tryAcquireSharedNanos(int arg, long nanosTimeout)
release(int arg) releaseShared(int arg)

上面列出了獨(dú)占模式和共享模式獲取鎖和釋放鎖的入口方法.我們對比分析就能很清楚的了解它們之間的不同.

共享模式獲取鎖

同獨(dú)占模式一樣,獲取鎖的入口方法我們從acquireShared(int arg)開始,另外還有兩個入口如果有興趣自己分析即可.

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

上面代碼第一步開始調(diào)用tryAcquireShared(arg)嘗試獲取共享資源,而在獨(dú)占模式中是調(diào)用tryAcquire(arg).同獨(dú)占模式一樣這個方法也需要同步器自己去實(shí)現(xiàn).這個方法返回值為剩余資源的個數(shù),主要可以分為三種情況:

  • 0:獲取共享資源成功,但是沒有剩余的資源了.
  • >0:獲取資源成功,還有剩余資源
  • <0:獲取資源失敗.

如果獲取資源成功則直接返回了,如果失敗了則進(jìn)入doAcquireShared(arg),而獨(dú)占模式下則是進(jìn)入acquireQueued(addWaiter(Node.EXCLUSIVE), arg).如果看了之前的文章的人知道下一步是干嘛了.下一個就是要將自己加入到隊(duì)列的尾部然后掛起等待.

    private void doAcquireShared(int arg) {
        //將自己添加到隊(duì)列的尾部,這里在獨(dú)占模式已分析過了,如果想了解請看之前獨(dú)占模式相關(guān)處
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            //同樣自旋
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    //如果自己是老二就有資格可以嘗試獲取資源
                    int r = tryAcquireShared(arg);
                    //如果資源還有剩余
                    if (r >= 0) {
                        //將自己設(shè)置為頭節(jié)點(diǎn),并傳播(后面會解釋傳播)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //嘗試獲取資源失敗,后續(xù)操作同獨(dú)占鎖
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

上面的代碼與獨(dú)占模式下整體上并沒有太大差別,主要流程如下:

  1. 調(diào)用addWaiter將自己添加到隊(duì)列的尾部
  2. 獲取當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn),然后看前節(jié)點(diǎn)是不是頭節(jié)點(diǎn).
  3. 如果當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)是頭節(jié)點(diǎn),自己就有資格去嘗試獲取資源.如果獲取失敗了就進(jìn)行后面操作邏輯.如果嘗試獲取資源成功了,則會調(diào)用setHeadAndPropagate(node, r)將自己設(shè)置成頭節(jié)點(diǎn)并往后傳播.關(guān)于setHeadAndPropagate這個方法后面會細(xì)講.
  4. 第2步判斷當(dāng)前節(jié)點(diǎn)不是老二或者第3步獲取資源失敗就會進(jìn)入這個邏輯.這個邏輯主要做兩件事:將前置節(jié)點(diǎn)設(shè)置waitStatuss設(shè)置成-1讓當(dāng)前線程掛起.這個邏輯與獨(dú)占鎖中的并沒有差別.

在第3步中調(diào)用tryAcquireShared獲取到資源后的操作與獨(dú)占模式中不一樣.在獨(dú)占模式中是調(diào)用setHead將自己設(shè)置成頭節(jié)點(diǎn),具體代碼如下:

    //獨(dú)占模式只會將當(dāng)前節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn)
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

而在共享模式中,它會調(diào)用setHeadAndPropagate不僅僅只是將當(dāng)前節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn),還有將當(dāng)前節(jié)點(diǎn)的后繼幾點(diǎn)繼續(xù)喚醒,具體代碼如下:

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head;
        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())
                //喚醒等待獲取共享資源的線程,后面細(xì)說.目前知道這個方法就是去喚醒等待的節(jié)點(diǎn)
                doReleaseShared();
        }
    }

收先要明確為什么能進(jìn)入這個方法,這個方法中的node一定是代表它是當(dāng)前成功獲取了共享資源的線程,propagate代表了當(dāng)前線程獲取了共享資源后還剩余的線程數(shù).這個方法一定是當(dāng)前線程成功獲取了共享資源才會進(jìn)來的.首先它通過一個h用來保存舊的head節(jié)點(diǎn),然后將自己設(shè)置成head節(jié)點(diǎn).然后進(jìn)入后面這一堆的判斷.下面我們來分析這一系列的判斷

  • propagate > 0:當(dāng)前線程獲取資源后發(fā)現(xiàn)還有剩余資源,那么這個時(shí)候就需要喚醒等待的節(jié)點(diǎn).這個很好理解,因?yàn)槭枪蚕砟J?當(dāng)資源有多余的時(shí)候就喚醒其他等待資源的線程.要不然怎么叫共享呢?
  • h.waitStatus < 0:這個代表老的head節(jié)點(diǎn)后面的節(jié)點(diǎn)可以被喚醒.
  • (h = head) == null || h.waitStatus < 0:這個代表新的head節(jié)點(diǎn)后面的節(jié)點(diǎn)需要被喚醒.

總結(jié)來說就是兩點(diǎn):當(dāng)propagate > 0時(shí)說明資源可用所以喚醒節(jié)點(diǎn),而h.waitStatus < 0說明不管是新的頭結(jié)點(diǎn)還是老的頭節(jié)點(diǎn)只要它的waitStatus < 0都需要喚醒節(jié)點(diǎn).

關(guān)于h == null這個條件我覺得不會生效.因?yàn)檫M(jìn)入該方法前addWaiter已經(jīng)調(diào)用了.CLH隊(duì)列中至少會存在一個節(jié)點(diǎn)所以我覺得這個條件不會生效.目前我也想不出什么情況下h == null.

上面就是整個共享模式下獲取資源的整個過程.整體上與獨(dú)占模式下差別不太大.都是獲取到了直接返回執(zhí)行業(yè)務(wù)代碼,獲取失敗則進(jìn)入阻塞.但是最大的不同點(diǎn)在于:共享模式下獲取共享資源成功的情況下同時(shí)還會去喚醒等待的線程,而在獨(dú)占模式下是不會的.

共享模式下釋放鎖

共享鎖的釋放的方法入口是releaseShared,它的源代碼如下:

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

該方法的實(shí)現(xiàn)比較簡單,tryReleaseShared(arg)(arg)這個是同步器需要自己實(shí)現(xiàn)的方法.釋放鎖時(shí)最核心的方法就是doReleaseShared().該方法在之前獲取共享資源時(shí)調(diào)用過,現(xiàn)在在釋放鎖的時(shí)候也調(diào)用過.我們來看該方法的實(shí)現(xiàn):

private void doReleaseShared() {
        for (;;) {
            //使用h保存久的頭節(jié)點(diǎn)
            Node h = head;
            //說明隊(duì)列種存在兩個以上的節(jié)點(diǎn)
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    //ws = -1 說明需要喚醒后續(xù)節(jié)點(diǎn),將h節(jié)點(diǎn)設(shè)置為0
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;
                    //在獨(dú)占模式已經(jīng)分析過了
                    unparkSuccessor(h);
                }
                //可能該節(jié)點(diǎn)被其他線程修改成0
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;
            }
            //說明上面的條件都沒有滿足的,所以退出
            if (h == head)
                break;
        }
    }

上面的代碼主要就是設(shè)置頭節(jié)點(diǎn)狀態(tài)為0,然后喚醒后續(xù)的節(jié)點(diǎn).其中關(guān)于PROPAGATE狀態(tài)的引入可以參照PROPAGATE狀態(tài)存在的意義.

小結(jié)

上面內(nèi)容基于獨(dú)占模式的對比做了共享模式下鎖的獲取和釋放.整體流程和獨(dú)占模式下大致相同,最大的不同點(diǎn)就在于共享模式成功獲取資源后還可能會喚醒后續(xù)等待的線程,而獨(dú)占模式是不會這樣做的.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市甫贯,隨后出現(xiàn)的幾起案子睁冬,更是在濱河造成了極大的恐慌安岂,老刑警劉巖介却,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件童叠,死亡現(xiàn)場離奇詭異结闸,居然都是意外死亡辩诞,警方通過查閱死者的電腦和手機(jī)坎弯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抠忘,你說我怎么就攤上這事撩炊。” “怎么了崎脉?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵拧咳,是天一觀的道長。 經(jīng)常有香客問我囚灼,道長骆膝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任啦撮,我火速辦了婚禮,結(jié)果婚禮上汪厨,老公的妹妹穿的比我還像新娘赃春。我一直安慰自己,他們只是感情好劫乱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布织中。 她就那樣靜靜地躺著,像睡著了一般衷戈。 火紅的嫁衣襯著肌膚如雪狭吼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天殖妇,我揣著相機(jī)與錄音刁笙,去河邊找鬼。 笑死谦趣,一個胖子當(dāng)著我的面吹牛疲吸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播前鹅,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼摘悴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舰绘?” 一聲冷哼從身側(cè)響起蹂喻,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捂寿,沒想到半個月后口四,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秦陋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年窃祝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡粪小,死狀恐怖大磺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情探膊,我是刑警寧澤杠愧,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站逞壁,受9級特大地震影響流济,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腌闯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一绳瘟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姿骏,春花似錦糖声、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嘲玫,卻和暖如春悦施,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背去团。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工抡诞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人土陪。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓沐绒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旺坠。 傳聞我的和親對象是個殘疾皇子乔遮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348