Java多線程(五)AQS-抽象隊(duì)列同步器解析

一、同步器設(shè)計(jì)

??AQS,全稱為AbstractQueuedSynchronizer(抽象隊(duì)列同步器),是Java多線程顯式鎖(Lock)的底層實(shí)現(xiàn)。同步器的設(shè)計(jì)屬于獨(dú)占模式:資源是獨(dú)占的,一次只能一個(gè)線程獲取。同步器的具體設(shè)計(jì)方案如下:

  1. 定義一個(gè)變量int state=0,變量表示為被獲取的資源數(shù)量。
  2. 線程在獲取資源前先檢查state的狀態(tài),如果為0,則修改為1,表示獲取資源成功,否則表示資源已經(jīng)被其他線程占用,此時(shí)線程要堵塞以等待其他線程釋放資源。
  3. 為了能使得資源釋放后找到那些為了等待資源而堵塞的線程,我們把這些線程保存在FIFO隊(duì)列中。
  4. 當(dāng)占有資源的線程釋放掉資源后,可以從隊(duì)列中喚醒一個(gè)堵塞的線程,由于此時(shí)資源已經(jīng)釋放,因此這個(gè)被喚醒的線程可以獲取資源并且執(zhí)行。

參考鏈接:JUC解析-AQS(1)

??AQS模型如圖1-1所示:

圖1-1 AQS模型.png

二、AQS成員變量

??AQS底層實(shí)現(xiàn)是雙向鏈表的數(shù)據(jù)結(jié)構(gòu),通過(guò)查看源碼,其主要包含的成員變量包括:

//狀態(tài)變量state
private volatile int state;
//雙向鏈表表頭
private transient volatile Node head;
//雙向鏈表表尾
private transient volatile Node tail;

??Node類(lèi)源碼如下所示:

static final class Node {
    //標(biāo)記一個(gè)結(jié)點(diǎn)(對(duì)應(yīng)的線程)在共享模式下等待
    static final Node SHARED = new Node();
   // 標(biāo)記一個(gè)結(jié)點(diǎn)(對(duì)應(yīng)的線程)在獨(dú)占模式下等待
    static final Node EXCLUSIVE = null;

    //waitStatus的值,表示該結(jié)點(diǎn)(對(duì)應(yīng)的線程)已被取消
    static final int CANCELLED =  1;
    //waitStatus的值,表示后繼結(jié)點(diǎn)(對(duì)應(yīng)的線程)需要被喚醒
    static final int SIGNAL    = -1;
    //waitStatus的值,表示該結(jié)點(diǎn)(對(duì)應(yīng)的線程)在等待某一條件
    static final int CONDITION = -2;
    //waitStatus的值,表示有資源可用,新head結(jié)點(diǎn)需要繼續(xù)喚醒后繼結(jié)點(diǎn)
    static final int PROPAGATE = -3;

    volatile int waitStatus;  //等待狀態(tài)
    volatile Node prev;  //前驅(qū)節(jié)點(diǎn)
    volatile Node next;  //后繼節(jié)點(diǎn)
    volatile Thread thread;  //節(jié)點(diǎn)對(duì)應(yīng)的線程
    
    Node nextWaiter;  //等待隊(duì)列下一個(gè)等待的節(jié)點(diǎn)
    //其余省略
    ... ... ... ...
}

三、acquire & release資源獲取模式

?? acquire是一種以獨(dú)占方式獲取資源,如果獲取到資源,線程直接返回,否則進(jìn)入等待隊(duì)列,直到獲取到資源為止,且整個(gè)過(guò)程忽略中斷的影響。acquire方法如下所示:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

1.tryAcquire()嘗試直接去獲取資源,如果成功則直接返回;
2.addWaiter()將該線程加入等待隊(duì)列的尾部,并標(biāo)記為獨(dú)占模式;
3.acquireQueued()使線程在等待隊(duì)列中獲取資源,一直獲取到資源后才返回。如果在整個(gè)等待過(guò)程中被中斷過(guò),則返回true,否則返回false。
4.如果線程在等待過(guò)程中被中斷過(guò),它是不響應(yīng)的。只是獲取資源后才再進(jìn)行自我中斷selfInterrupt(),將中斷補(bǔ)上。

??release方法會(huì)釋放指定量的資源,如果徹底釋放了(即state=0),它會(huì)喚醒等待隊(duì)列里的其他線程來(lái)獲取資源。release方法源碼如下所示:

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

??如果資源釋放成功,調(diào)用unparkSuccessor(Node)方法喚醒等待隊(duì)列中下一個(gè)線程。這里要注意的是,下一個(gè)線程并不一定是當(dāng)前節(jié)點(diǎn)的next節(jié)點(diǎn),而是下一個(gè)可以用來(lái)喚醒的線程,如果這個(gè)節(jié)點(diǎn)存在,調(diào)用unpark()方法喚醒。unparkSuccessor方法源碼如下所示:

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 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);
}

參考鏈接:Java技術(shù)之AQS詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容