AQS詳解

簡(jiǎn)介

AQS(AbstractQueuedSynchronizer) 是通過(guò)FIFO隊(duì)列實(shí)現(xiàn)的一套框架,用于阻塞鎖以及同步器比如semaphores代承、countdownlatch现拒、lock的實(shí)現(xiàn)均依賴它里逆。它通過(guò)一個(gè)原子的int值實(shí)現(xiàn)的同步,子類可以通過(guò)保護(hù)方法改變?cè)搃nt變量的值。
它提供了兩種模式顽照,分別為獨(dú)占模式和共享模式,獨(dú)占模式只允許被一個(gè)線程獲取到而共享模式則允許多個(gè)線程獲取。本文主要介紹的幾個(gè)方法就是這兩種模式的體現(xiàn)代兵。

獨(dú)占模式

實(shí)現(xiàn)方式:一個(gè)線程獲取到鎖并釋放后尼酿,后續(xù)線程才能獲取到鎖,在隊(duì)列中表示為當(dāng)頭結(jié)點(diǎn)將鎖釋放后會(huì)將后繼結(jié)點(diǎn)喚醒

  1. void acquire(int arg)
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
# tryAcquire 為具體實(shí)現(xiàn)植影,就是對(duì)state變量的操作

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                #前置節(jié)點(diǎn)
                final Node p = node.predecessor();
                #前置節(jié)點(diǎn)為頭節(jié)點(diǎn)并且資源可以獲取到
                if (p == head && tryAcquire(arg)) {
                    #將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)裳擎,前置節(jié)點(diǎn)不再維護(hù)了
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                 #shouldParkAfterFailedAcquire作用就是將前置節(jié)點(diǎn)狀態(tài)置為SIGNAL,下面詳細(xì)分析思币,parkAndCheckInterrupt是將當(dāng)前線程阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
           #節(jié)點(diǎn)狀態(tài)為SIGNAL時(shí)返回
            return true;
        if (ws > 0) {
           # 將取消的節(jié)點(diǎn)跳過(guò)
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
           #前置節(jié)點(diǎn)狀態(tài)設(shè)置為SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

舉例說(shuō)明鹿响,當(dāng)有兩個(gè)線程A、B調(diào)用tryAcquire方法時(shí)谷饿,A惶我、B按照順序依次入隊(duì)新來(lái)的始終排在隊(duì)尾,只要A還沒(méi)有獲取到博投,那么A肯定是阻塞的绸贡,它的后繼節(jié)點(diǎn)也肯定是阻塞的
畫個(gè)圖表示下:


image.png
  1. boolean release(int arg)
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
#tryRelease為具體實(shí)現(xiàn),也是對(duì)state變量的操作

它的實(shí)現(xiàn)也是比較簡(jiǎn)單就是當(dāng)資源拿到時(shí)毅哗,直接將鏈表的頭部節(jié)點(diǎn)的后繼節(jié)點(diǎn)喚醒听怕,喚醒代碼如下:

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            #頭節(jié)點(diǎn)狀態(tài)置為0
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

當(dāng)后繼節(jié)點(diǎn)被喚醒后,那么該節(jié)點(diǎn)對(duì)應(yīng)的線程會(huì)繼續(xù)執(zhí)行acquireQueued方法中的for循環(huán)虑绵,這時(shí)當(dāng)拿到資源后尿瞭,那么就會(huì)將該節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),返回成功了


image.png

共享模式

實(shí)現(xiàn)方式:一個(gè)線程獲取到鎖后翅睛,會(huì)通知后繼結(jié)點(diǎn)筷厘,后繼結(jié)點(diǎn)獲取到鎖后繼續(xù)向后傳播,這樣就能使得多個(gè)線程獲取到同一個(gè)鎖

  1. void acquireShared(int arg)
 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
# tryAcquire 為具體實(shí)現(xiàn)宏所,就是對(duì)state變量的操作
#與acquireQueued的不同之處在于酥艳,該方法加入的節(jié)點(diǎn)類型為SHARED,另外就是在獲取到資源>0時(shí)爬骤,該方法會(huì)將做傳播操作
private void doAcquireShared(int arg) {
        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) {
                        #r>0當(dāng)前節(jié)點(diǎn)置為頭節(jié)點(diǎn)充石,并傳播,下面細(xì)說(shuō)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }


private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        #當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)
        setHead(node);
        #當(dāng)獲取資源數(shù)大于0 或者頭部不存在或者之前的以及當(dāng)前的頭節(jié)點(diǎn)狀態(tài)小于0霞玄,則當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)需要被喚醒
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            #后繼節(jié)點(diǎn)為共享類型
            if (s == null || s.isShared())
                doReleaseShared();
        }

#作用就是將頭節(jié)點(diǎn)狀態(tài)從SIGNAL置為0骤铃,并喚醒后繼節(jié)點(diǎn),
#再將頭節(jié)點(diǎn)狀態(tài)置為PROPAGATE坷剧,這樣當(dāng)后繼節(jié)點(diǎn)被喚醒后惰爬,
#它獲取到資源后把自己置為頭結(jié)點(diǎn),繼續(xù)重復(fù)之前動(dòng)作喚醒后繼節(jié)點(diǎn)
private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
  1. boolean releaseShared(int arg)
    上邊已經(jīng)介紹

過(guò)程如下圖所示
初始階段有3個(gè)線程都來(lái)調(diào)用acquireShared時(shí)惫企,狀態(tài)如下


image.png

當(dāng)A獲取到資源時(shí)撕瞧,則


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末陵叽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丛版,更是在濱河造成了極大的恐慌巩掺,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件页畦,死亡現(xiàn)場(chǎng)離奇詭異胖替,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)豫缨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門独令,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人好芭,你說(shuō)我怎么就攤上這事记焊。” “怎么了栓撞?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵遍膜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我瓤湘,道長(zhǎng)瓢颅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任弛说,我火速辦了婚禮挽懦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘木人。我一直安慰自己信柿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布醒第。 她就那樣靜靜地躺著渔嚷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稠曼。 梳的紋絲不亂的頭發(fā)上形病,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音霞幅,去河邊找鬼漠吻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛司恳,可吹牛的內(nèi)容都是我干的途乃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扔傅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耍共!你這毒婦竟也來(lái)了烫饼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤划提,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后邢享,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹏往,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年骇塘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伊履。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡款违,死狀恐怖唐瀑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情插爹,我是刑警寧澤哄辣,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站赠尾,受9級(jí)特大地震影響力穗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜气嫁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一当窗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寸宵,春花似錦崖面、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至甲棍,卻和暖如春疏遏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背救军。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工财异, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唱遭。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓戳寸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拷泽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疫鹊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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