java并發(fā)之AbstractQueuedSynchronizer

引言

可能我們平常很少聽說(shuō)它,就算是在并發(fā)里也很少用到它湘捎,但是我們所用的Condition(條件)、ReentrantLock(可重入鎖)舷胜、ReentrantReadWriteLock(讀寫鎖)就是根據(jù)它所構(gòu)建的活翩,它叫做隊(duì)列同步器,是構(gòu)建其他鎖和同步組件的基礎(chǔ)框架材泄。

同步器的主要使用方法是繼承,子類通過(guò)繼承同步器并實(shí)現(xiàn)它的抽象方法來(lái)管理同步狀態(tài)峦树,在抽象方法的實(shí)現(xiàn)過(guò)程中免不了對(duì)同步狀態(tài)進(jìn)行更改旦事,就需要使用同步器提供的3個(gè)最主要的方法

 protected final int getState() ; //獲取同步狀態(tài)
 protected final void setState(int newState) ;//設(shè)置同步狀態(tài)
 protected final boolean compareAndSetState(int expect, int update) 族檬;//原子性設(shè)置同步狀態(tài)(使用CAS)

AQS本身是沒有實(shí)現(xiàn)任何同步接口的,它僅僅是定義了若干同步狀態(tài)的獲取和釋放來(lái)供自定義同步組件使用埋凯,它可以支持獨(dú)占式的獲取同步狀態(tài)(只能有一個(gè)線程使用,其他都得等待)白对,也支持共享式的獲取同步狀態(tài)(如讀寫鎖中的讀鎖)

同步器的設(shè)計(jì)是采用的模板方法設(shè)計(jì)模式,我們只需要重寫指定的方法蟀瞧,隨后將同步器組合在自定義同步組件中条摸,并調(diào)用模板方法提供的模板方法即可。

源碼分析

AQS里面可以重寫的主要方法如下(需要我們自己實(shí)現(xiàn)邏輯):

        protected boolean tryAcquire(int arg) ; //獨(dú)占式的獲取鎖

        protected boolean tryRelease(int arg) ;//獨(dú)占式的釋放鎖
       
        protected int tryAcquireShared(int arg) ;//共享式的獲取鎖

        protected boolean tryReleaseShared(int arg) ; //共享式的釋放鎖
      
        protected boolean isHeldExclusively() ; //判斷當(dāng)前線程是否是在獨(dú)占模式下被線程占用钉蒲,一般表示是否被當(dāng)前線程所占用
       

AQS提供的主要模板方法如下(我們不能重寫):

   public final void acquire(int arg) ; //獨(dú)占式獲取鎖

   public final void acquireInterruptibly(int arg); //與acquire(int arg)一樣顷啼,但響應(yīng)中斷

   public final boolean tryAcquireNanos(int arg, long nanosTimeout);//在acquireInterruptibly(int arg基礎(chǔ)上加入了超時(shí)機(jī)制,在規(guī)定時(shí)間內(nèi)沒有獲取到鎖返回false
獲取到了返回true

   public final boolean release(int arg) ;//獨(dú)占式的釋放同步狀態(tài)

   public final void acquireShared(int arg) ; //共享式的獲取鎖

   public final void acquireSharedInterruptibly(int arg);  //在acquireShared(int arg)加入了對(duì)中斷的響應(yīng)

  public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)  //在acquireSharedInterruptibly(int arg)基礎(chǔ)上加入超時(shí)機(jī)制

  public final boolean releaseShared(int arg) ; //共享式的釋放鎖

這上面的所有方法都加上了final,表明了它們不可繼承钙蒙,這就是模板方法設(shè)計(jì)模式

下面我們來(lái)看一下它的源碼

  • 首先是獨(dú)占式同步鎖的獲取
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

這是獨(dú)占式獲取鎖的入口

具體流程如下:
 1躬厌、用tryacquire(arg)去獲取同步狀態(tài)(需要我們自己實(shí)現(xiàn))
 2、獲取不到把線程封裝Node節(jié)點(diǎn)(addwaiter),在加入等待隊(duì)列中
  • tryAcquire(int arg)源碼
protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

源碼就是簡(jiǎn)單地拋出異常烤咧,所以需要我們自己去實(shí)現(xiàn)

  • addWaiter 源碼
 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            //嘗試加入到隊(duì)尾抢呆,因?yàn)檫@可能有多個(gè)線程競(jìng)爭(zhēng),采用compareAndSetTail(CAS操作)
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
      //如果表頭為空又或者節(jié)點(diǎn)沒加入到隊(duì)尾
        enq(node);
        return node;
    }

先構(gòu)造成Node節(jié)點(diǎn)

  • Node是其內(nèi)部類,主要構(gòu)造如下
static final class Node {
  volatile int waitStatus;
  volatile Node prev;
  volatile Node next;
  volatile Thread thread;
  Node nextWaiter;
}
  • prev:前驅(qū)節(jié)點(diǎn)昌阿;

  • next:后繼節(jié)點(diǎn)恳邀;

  • thread:進(jìn)入隊(duì)列的當(dāng)前線程

  • nextWaiter:存儲(chǔ)condition隊(duì)列中的后繼節(jié)點(diǎn)。

    • waitStatus:節(jié)點(diǎn)狀態(tài)谣沸,主要有這幾種狀態(tài):
      1、 CANCELLED:當(dāng)前線程被取消内地;
      2、SIGNAL:當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)需要運(yùn)行阱缓;
      3、 CONDITION:當(dāng)前節(jié)點(diǎn)在等待condition
      4敞嗡、PROPAGATE:當(dāng)前場(chǎng)景下后續(xù)的acquireShared可以執(zhí)行航背;

隊(duì)列的基本結(jié)構(gòu)如下:

3994601-525f0d33e1512800.png
  • enq源碼
/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
     //死循環(huán)(自旋)地插入到隊(duì)尾
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                //通過(guò)compareAndSetTail保證節(jié)點(diǎn)能被線程安全添加
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

設(shè)置尾節(jié)點(diǎn)的過(guò)程

3994601-f9f4486a56dc72f4.png

加入到同步隊(duì)列中之后沃粗,就執(zhí)行 acquireQueued

  • acquireQueued源碼

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

節(jié)點(diǎn)進(jìn)入同步隊(duì)列后,就開始自旋突雪,每個(gè)節(jié)點(diǎn)都在觀察自己是否滿足條件涡贱,當(dāng)“條件”滿足,就可以獲取同步狀態(tài)问词,然后從自旋地過(guò)程中退出

這里有兩點(diǎn)要說(shuō)明:
1、頭節(jié)點(diǎn)是獲取到同步狀態(tài)的點(diǎn)激挪,而頭節(jié)點(diǎn)的線程釋放同步狀態(tài)后,會(huì)喚醒其后續(xù)節(jié)點(diǎn)宛篇,所以每個(gè)節(jié)點(diǎn)都在判斷自己的前驅(qū)節(jié)點(diǎn)是否是頭節(jié)點(diǎn)薄湿,是之后看能不能獲取到同步狀態(tài),只有兩者都滿足時(shí)豺瘤,才能退出
2、維護(hù)同步隊(duì)列的是fifo原則(先進(jìn)先出)蚕泽,整個(gè)過(guò)程如圖所示:

3994601-032edf539ec5d54b.png

總結(jié)一下獨(dú)占式獲取鎖的流程:

3994601-59833f358ae1cf04.png

當(dāng)前線程獲取到同步狀態(tài)后桥嗤,就需要釋放同步狀態(tài)派任,使后續(xù)節(jié)點(diǎn)能夠獲取璧南,主要是通過(guò)release(int arg) 實(shí)現(xiàn)的

  • 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:判斷能否釋放同步狀態(tài)
unparkSuccessor(h):?jiǎn)拘押罄m(xù)節(jié)點(diǎn)

上面講了獨(dú)占式獲取獲取鎖,下面講講共享式獲取

共享式和獨(dú)占式最大的區(qū)別是同一時(shí)刻能否有多個(gè)線程同時(shí)獲取到同步狀態(tài)豆混,如圖所示

3994601-af326de0d86eb2e9.png

共享式獲取的入口是acquireShared 方法

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

tryAcquireShared方法是需要我們自己實(shí)現(xiàn)的动知,返回的是int值,當(dāng)返回值大于0時(shí)盒粮,表示能獲取到同步狀態(tài)

  • doAcquireShared源碼
 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) {
                        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);
        }
    }

先把線程加入到同步隊(duì)列中丹皱,然后死循環(huán),判斷前驅(qū)節(jié)點(diǎn)是否是頭節(jié)點(diǎn)摊崭,然后獲取同步狀態(tài),當(dāng)兩者都滿足是就退出循環(huán)

與獨(dú)占式獲取同步狀態(tài)一樣矮台,共享式獲取也是需要釋放同步狀態(tài)的,AQS提供releaseShared(int arg)方法可以釋放同步狀態(tài)瘦赫。

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

1蛤迎、調(diào)用tryReleaseShared方法釋放狀態(tài);
2忘苛、 調(diào)用doReleaseShared方法喚醒后繼節(jié)點(diǎn)唱较;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市胸遇,隨后出現(xiàn)的幾起案子汉形,更是在濱河造成了極大的恐慌倍阐,老刑警劉巖逗威,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凯旭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)罐呼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門嫉柴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人计螺,你說(shuō)我怎么就攤上這事〉锹” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵肺孤,是天一觀的道長(zhǎng)济欢。 經(jīng)常有香客問(wèn)我,道長(zhǎng)法褥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任揍愁,我火速辦了婚禮杀饵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘切距。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布北秽。 她就那樣靜靜地躺著最筒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪床蜘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天虏冻,我揣著相機(jī)與錄音弹囚,去河邊找鬼。 笑死鸥鹉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毁渗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼府适,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肺樟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起么伯,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俐巴,沒想到半個(gè)月后硬爆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摆屯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年虐骑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了准验。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廷没。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颠黎,死狀恐怖另锋,靈堂內(nèi)的尸體忽然破棺而出狭归,到底是詐尸還是另有隱情,我是刑警寧澤过椎,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站亡鼠,受9級(jí)特大地震影響敷待,放射性物質(zhì)發(fā)生泄漏间涵。R本人自食惡果不足惜榜揖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钳幅。 院中可真熱鬧炎滞,春花似錦敢艰、人聲如沸册赛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至逮栅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間措伐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工捧存, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昔穴。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓提前,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親狈网。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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