AbstractQueuedSynchronizer 隊(duì)列同步器源碼分析

AbstractQueuedSynchronizer 隊(duì)列同步器(AQS)

隊(duì)列同步器 (AQS), 是用來構(gòu)建鎖或其他同步組件的基礎(chǔ)框架侦锯,它通過使用 int 變量表示同步狀態(tài)吱抚,通過內(nèi)置的 FIFO 的隊(duì)列完成資源獲取的排隊(duì)工作剩膘。(摘自《Java并發(fā)編程的藝術(shù)》)

我們知道獲取同步狀態(tài)有獨(dú)占和共享兩種模式仅叫,本文先針對獨(dú)占模式進(jìn)行分析庞萍。

變量定義

private transient volatile Node head;

head 同步隊(duì)列頭節(jié)點(diǎn)

private transient volatile Node tail;

tail 同步隊(duì)列尾節(jié)點(diǎn)

private volatile int state;

state 同步狀態(tài)值

Node - 同步隊(duì)列節(jié)點(diǎn)定義

volatile int waitStatus;

waitStatus 節(jié)點(diǎn)的等待狀態(tài),可取值如下 :

  • 0 : 初始狀態(tài)
  • -1 : SIGNAL 處于該狀態(tài)的節(jié)點(diǎn)痛倚,說明其后置節(jié)點(diǎn)處于等待狀態(tài)丰刊; 若當(dāng)前節(jié)點(diǎn)釋放了鎖可喚醒后置節(jié)點(diǎn)
  • -2 : CONDITION 該狀態(tài)與 Condition 操作有關(guān)后續(xù)在說明
  • -3 : PROPAGATE 該狀態(tài)與共享式獲取同步狀態(tài)操作有關(guān)后續(xù)在說明
  • 1 : CANCELLED 處于該狀態(tài)的節(jié)點(diǎn)會取消等待,從隊(duì)列中移除
volatile Node prev;

prev 指向當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)

volatile Node next;

next 指向當(dāng)前節(jié)點(diǎn)的后置節(jié)點(diǎn)

volatile Thread thread;

thread 節(jié)點(diǎn)對應(yīng)的線程也是指當(dāng)前獲取鎖失敗的線程

Node nextWaiter;

acquire()

獨(dú)占模式下獲取同步狀態(tài)瞒斩, 既是當(dāng)前只允許一個線程獲取到同步狀態(tài)

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

從 acquire 方法中我們可以大概猜測下破婆,獲取鎖的過程如下:

  • tryAcquire 嘗試獲取同步狀態(tài), 具體如何判定獲取到同步狀態(tài)由子類實(shí)現(xiàn)
  • 當(dāng)獲取同步狀態(tài)失敗時胸囱,執(zhí)行 addWaiter 創(chuàng)建獨(dú)占模式下的 Node 并將其添加到同步隊(duì)列尾部
  • 加入同步隊(duì)列之后荠割,再次嘗試獲取同步狀態(tài),當(dāng)達(dá)到某種條件的時候?qū)?dāng)前線程掛起等待喚醒

下面具體看下各個階段如何實(shí)現(xiàn):

private Node addWaiter(Node mode) {
    // 綁定當(dāng)前線程 創(chuàng)建 Node 節(jié)點(diǎn)
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // 判斷同步隊(duì)列尾節(jié)點(diǎn)是否為空
    if (pred != null) {
        // node 的前置節(jié)點(diǎn)指向隊(duì)列尾部
        node.prev = pred;
        // 將同步隊(duì)列的 tail 移動指向 node
        if (compareAndSetTail(pred, node)) {
            // 將原同步隊(duì)列的尾部后置節(jié)點(diǎn)指向 node
            pred.next = node;
            return node;
        }
    }
    // tail 為空說明同步隊(duì)列還未初始化
    // 此時調(diào)用 enq 完成隊(duì)列的初始化及 node 入隊(duì)
    enq(node);
    return node;
}
private Node enq(final Node node) {
    // 輪詢的方式執(zhí)行
    // 成功入隊(duì)后退出
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // 創(chuàng)建 Node, 并將 head 指向該節(jié)點(diǎn)
            // 同時將 tail 指向該節(jié)點(diǎn)
            // 完成隊(duì)列的初始化
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // node 的前置節(jié)點(diǎn)指向隊(duì)列尾部
            node.prev = t;
            // 將同步隊(duì)列的 tail 移動指向 node
            if (compareAndSetTail(t, node)) {
                // 將原同步隊(duì)列的尾部后置節(jié)點(diǎn)指向 node
                t.next = node;
                return t;
            }
        }
    }
}

從代碼中可以看出通過 CAS 操作保證節(jié)點(diǎn)入隊(duì)的有序安全旺矾,其入隊(duì)過程中如下圖所示:

AQS節(jié)點(diǎn)入隊(duì)過程
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 
        for (;;) {
            // 獲取當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)
            final Node p = node.predecessor();
            // 判斷前置節(jié)點(diǎn)是否為 head 頭節(jié)點(diǎn)
            // 若前置節(jié)點(diǎn)為 head 節(jié)點(diǎn)蔑鹦,則再次嘗試獲取同步狀態(tài)
            if (p == head && tryAcquire(arg)) {
                // 若獲取同步狀態(tài)成功
                // 則將隊(duì)列的 head 移動指向當(dāng)前節(jié)點(diǎn)
                setHead(node);
                // 將原頭部節(jié)點(diǎn)的 next 指向?yàn)榭眨阌趯ο蠡厥?                p.next = null; // help GC
                failed = false;
                // 退出輪詢過程
                return interrupted;
            }
            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)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        // 若前置節(jié)點(diǎn)狀態(tài)為 -1 箕宙,則說明后置節(jié)點(diǎn) node 可以安全掛起了
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            // ws > 0 說明前置節(jié)點(diǎn)狀態(tài)為 CANCELLED , 也就是說前置節(jié)點(diǎn)為無效節(jié)點(diǎn)
            // 此時從前置節(jié)點(diǎn)開始向隊(duì)列頭節(jié)點(diǎn)方向?qū)ふ矣行У那爸霉?jié)點(diǎn)
            // 此操作也即是將 CANCELLED 節(jié)點(diǎn)從隊(duì)列中移除
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        // 若前置節(jié)點(diǎn)狀態(tài)為初始狀態(tài) 則將其狀態(tài)設(shè)為 -1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
private final boolean parkAndCheckInterrupt() {
    // 將當(dāng)前線程掛起
    LockSupport.park(this);
    // 被喚醒后檢查當(dāng)前線程是否被掛起
    return Thread.interrupted();
}

從 acquireQueued 的實(shí)現(xiàn)可以看出嚎朽,節(jié)點(diǎn)在入隊(duì)后會采用輪詢的方式(自旋)重復(fù)執(zhí)行以下過程:

  • 判斷前置節(jié)點(diǎn)是否為 head, 若為 head 節(jié)點(diǎn)則嘗試獲取同步狀態(tài)柬帕; 若獲取同步狀態(tài)成功則移動 head 指向當(dāng)前節(jié)點(diǎn)并退出循環(huán)
  • 若前置節(jié)點(diǎn)非 head 節(jié)點(diǎn)或者獲取同步狀態(tài)失敗哟忍,則將前置節(jié)點(diǎn)狀態(tài)修改為 -1, 并掛起當(dāng)前線程陷寝,等待被喚醒重復(fù)執(zhí)行以上過程

如下圖所示:

AQS-節(jié)點(diǎn)自旋活動圖

接下來我們看看同步狀態(tài)釋放的實(shí)現(xiàn)锅很。

release

釋放同步狀態(tài)

public final boolean release(int arg) {
    // 嘗試釋放同步狀態(tài)
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 喚醒后置節(jié)點(diǎn)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        // 將 head 節(jié)點(diǎn)狀態(tài)改為 0
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    // 獲取后置節(jié)點(diǎn)
    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)
        // 喚醒后置節(jié)點(diǎn)上所阻塞的線程
        LockSupport.unpark(s.thread);
}

從上述代碼,我們可以明白釋放同步狀態(tài)的過程如下:

  • 調(diào)用 tryRelease 嘗試釋放同步狀態(tài)凤跑,同樣其具體的實(shí)現(xiàn)由子類控制
  • 成功釋放同步狀態(tài)后爆安,將 head 節(jié)點(diǎn)狀態(tài)改為 0
  • 喚醒后置節(jié)點(diǎn)上阻塞的線程

如下圖所示(紅色曲線表示節(jié)點(diǎn)自旋過程) :

AQS-釋放鎖

acquireInterruptibly()

獨(dú)占模式下獲取同步狀態(tài),不同于 acquire 方法仔引,該方法對中斷操作敏感扔仓; 也就是說當(dāng)前線程在獲取同步狀態(tài)的過程中褐奥,若被中斷則會拋出中斷異常

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
    if (Thread.interrupted())
        // 檢查線程是否被中斷
        // 中斷則拋出中斷異常由調(diào)用方處理
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 不同于 acquire 的操作,此處在喚醒后檢查是否中斷翘簇,若被中斷直接拋出中斷異常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            // 拋出中斷異常后最終執(zhí)行 cancelAcquire
            cancelAcquire(node);
    }
}
private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    // 若當(dāng)前節(jié)點(diǎn)為 tail 節(jié)點(diǎn)撬码,則將 tail 移動指向 node 的前置節(jié)點(diǎn)
    if (node == tail && compareAndSetTail(node, pred)) {
        // 同時將node 前置節(jié)點(diǎn)的 next 指向 null
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            // 當(dāng)前節(jié)點(diǎn)位于隊(duì)列中部    
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                // 將前置節(jié)點(diǎn)的 next 指向 node 的后置節(jié)點(diǎn)
                compareAndSetNext(pred, predNext, next);
        } else {
            // 若 node 的前置節(jié)點(diǎn)為 head 節(jié)點(diǎn)則喚醒 node 節(jié)點(diǎn)的后置節(jié)點(diǎn)
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

從 acquireInterruptibly 的實(shí)現(xiàn)可以看出,若線程在獲取同步狀態(tài)的過程中出現(xiàn)中斷操作版保,則會將當(dāng)前線程對應(yīng)的同步隊(duì)列等待節(jié)點(diǎn)從隊(duì)列中移除并喚醒可獲取同步狀態(tài)的線程呜笑。

tryAcquireNanos()

獨(dú)占模式超時獲取同步狀態(tài),該操作與acquireInterruptibly一樣對中斷操作敏感彻犁,不同在于超過等待時間若未獲取到同步狀態(tài)將會返回

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 計(jì)算等待到期時間
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                // 超時時間到期直接返回
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                // 按指定時間掛起s
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

節(jié)點(diǎn)的狀態(tài)

同步隊(duì)列中的節(jié)點(diǎn)在自旋獲取同步狀態(tài)的過程中叫胁,會將前置節(jié)點(diǎn)的狀態(tài)由 0 初始狀態(tài)改為 -1 SIGNAL, 若是中斷敏感的操作則會將狀態(tài)由 0 改為 1

同步隊(duì)列中的節(jié)點(diǎn)在釋放同步狀態(tài)的過程中會將同步隊(duì)列的 head 節(jié)點(diǎn)的狀態(tài)改為 0, 也即是由 -1 變?yōu)?0袖裕;

小結(jié)

本文主要分析了獨(dú)占模式獲取同步狀態(tài)的操作,其大概流程如下:

  • 在獲取同步狀態(tài)時溉瓶,AQS 內(nèi)部維護(hù)了一個同步隊(duì)列急鳄,獲取狀態(tài)失敗的線程會被構(gòu)造一個節(jié)點(diǎn)加入到隊(duì)列中并進(jìn)行一系列自旋操作
  • 在釋放同步狀態(tài)時,喚醒 head 的后置節(jié)點(diǎn)去獲取同步狀態(tài)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堰酿,一起剝皮案震驚了整個濱河市疾宏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌触创,老刑警劉巖坎藐,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哼绑,居然都是意外死亡岩馍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門抖韩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛀恩,“玉大人,你說我怎么就攤上這事茂浮∷唬” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵席揽,是天一觀的道長顽馋。 經(jīng)常有香客問我,道長幌羞,這世上最難降的妖魔是什么寸谜? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮属桦,結(jié)果婚禮上程帕,老公的妹妹穿的比我還像新娘住练。我一直安慰自己,他們只是感情好愁拭,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布讲逛。 她就那樣靜靜地躺著,像睡著了一般岭埠。 火紅的嫁衣襯著肌膚如雪盏混。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天惜论,我揣著相機(jī)與錄音许赃,去河邊找鬼。 笑死馆类,一個胖子當(dāng)著我的面吹牛混聊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乾巧,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼句喜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沟于?” 一聲冷哼從身側(cè)響起咳胃,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旷太,沒想到半個月后展懈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡供璧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年存崖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睡毒。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡金句,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吕嘀,到底是詐尸還是另有隱情违寞,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布偶房,位于F島的核電站趁曼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏棕洋。R本人自食惡果不足惜挡闰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摄悯,春花似錦赞季、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘪阁,卻和暖如春撒遣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背管跺。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工义黎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豁跑。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓廉涕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艇拍。 傳聞我的和親對象是個殘疾皇子狐蜕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

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