AbstractQueuedSynchroizer(AQS) 同步器詳解詳解

一、什麼是同步器
同步器是用來構建鎖或者其他同步組件的基礎框架,它使用一個int成員變量表示同步狀態,通過內置的FIFO隊列來完成資源獲取線程的排隊工作,它能實現大部分的同步需求。
同步器是實現鎖的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的語義。可以這樣理解二者的關係:鎖是面向使用者的,它定義了使用者與鎖交互的接口(比如可以允許兩個線程的並行訪問),隱藏了實現細節;同步器面向的是鎖的實現者,它簡化了鎖的實現方式,屏蔽了狀態管理、線程的排隊、等待與喚醒等底層操作。鎖和同步容器很好地隔離了使用者和實現者所需要關注的領域。
二、同步器的基本成員(介紹常用的類好方法)
Node 是AQS的內部類構成AQS隊列的一種數據結構。

成員變量 作用
waitStatus 記錄節點的等待狀態。包括如下狀態:① CANCELLED,值爲1,由於同步隊列中等待線程超時或者被中斷,需要從同步隊列中取消等待,節點進入該狀態將不會變化。② SIGNAL值爲-1,後繼節點的線程處於等待狀態,而當前線程如果釋放了同步狀態或者取消,將會通知後繼節點,使得後繼節點得以運行。③ CONDITION值爲-2,節點在等待隊列中,節點等待在Condtion上,當其他線程對Condtion調用了signal方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到對同步狀態的獲取中。④ PROPAGATE值爲-3,表示下一次共享式同步狀態將會無條件地被傳播下去。⑤ INITAL,值爲0,初始狀態
SHARED = new Node() 表示共享式的node
EXCLUSIVE = null 獨佔式的node
Node prev Node的前節點
Node next Node的後節點
nextWaitert 等待隊列的中node的下一個節點

ConditionObject是AQS的內部類構成類似Object的等待/通知機制。

成員/方法 作用
Node firstWaiter 等待隊列的頭節點
Node lastWaiter 等待隊列的尾節點
await() 當前線程進入等待狀態知道被通知或中斷,當前線程進入運行狀態且從await()返回的情況如下,包括:① 其它線程調用Interrupt()方法中斷當前線程。② 如果當前線程從await()方法返回,那麼表明該線程已經獲取了Condtion對象鎖對應的鎖
awaitUninterruptibly() 當前線程進入等待直到被通知,該方法對中斷不敏感
awaitNanos(long nanosTimeout)) 當前線程進入等待狀態直到被通知、中斷或者超時。返回值表示剩餘的時間,如果在nanosTimeout納秒之前被喚醒,那麼返回就是(nanosTimeout-實際耗時);如果返回是0或者負數,那麼可以認定已經超時了
awaitUntil(Date deadline) 當前線程進入等待狀態直到被通知、中斷或者某個時間。如果沒有到指定時間就被通知,方法返回true,否則,表示超時,方法返回false
signal() 喚醒一個等待在Condition上的線程,該線程從等待方法返回前必須獲得與Condition相關的鎖
signalAll 喚醒所有等待在Condition上的線程,能夠從等待方法返回的線程必須獲得與Condition相關聯的鎖

AQS主要成員

成員變量 作用
state 維護鎖的一個變量(同步狀態,很重要)① setState 。② getState。 ③ compareAndSetState。
Node head FIFO同步隊列的頭結點 。
Node tail FIFO同步隊列的尾結點 。

AQS主要方法

方法名 作用
acquire() 獨佔式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回,否則,將會進入同步隊列等待,該方法會調用重寫的tryAcquire(arg)方法(需要鎖自己實現)
release(int arg) 獨佔式釋放狀態,如果釋放狀態成功,則會去喚醒頭結點;釋放狀態調用tryRelease(arg)方法(需要自己實現)
acquireShared(int arg) 共享式獲取同步狀態,也就是說可以幾個線程同時獲取同步狀態,如果當前線程未獲取同步狀態,將會進入同步隊列。
releaseShared() 共享式釋放狀態,釋放之後會喚醒頭結點
acquireInterruptibly() 響應中斷的獨佔式獲取同步狀態,當前線程未獲取同步狀態而進入同步隊列中,如果當前線程被中斷,則該方法會拋出中斷異常,並返回
tryAcquireNanos() 在acquireInterruptibly()基礎上增加超時限制,如果當前線程在超時時間內沒有獲取同步狀態,那麼將返回false,如果獲取到了返回true
acquireSharedInterruptibly() 響應中斷的共享式獲取同步狀態
tryAcquireSharedNanos() 在acquireSharedInterruptibly()的基礎上增加超時限制

以上就是AQS的一些基本成員和方法,下面主要從現實的角度分析這些方法,理解這些方法的實現,能剛好的幫助我們去理解鎖。
三、AQS的方法實現分析
1)、獨佔系列的方法
①、acquire()獨佔式獲取同步狀態,表示只會有一個線程獲取,其它線程進入同步隊列。
源代碼如下:

// 獲取鎖的方法(獨佔模式)
    public final void acquire(int arg) {
        // tryAcquire(arg) 這個方法需要我們自己去實現,如果獲取失敗,
        // 調用addWaiter構造節點
        // acquireQueued
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

我們可以看見acquire方法內部調用了tryAcquire(arg)方法,這個方法需要構造同步組件的類自己去實現,不過返回值已經被AQS定義好了,返回true代表獲取同步狀態成功,返回false代表失敗,需要將線程構造節點加入同步隊列,就是調用acquireQueued這個方法。
acquireQueued這個方法實際是先去調用了addWaiter方法。
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節點的上一個節點爲pred(也就是尾節點)
            node.prev = pred;
            // cas設置尾節點
            if (compareAndSetTail(pred, node)) {
                // 成功後,設置pred節點的next節點爲node,返回
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

acquireQueued()方法,通過上面的addWaiter方法我們已經把這個節點加入同步隊列,接下來需要處理這個節點。首先判斷自己的前節點是否是頭結點,是否獲取到同步狀態,如果滿足,把自己設置尾頭結點,返回,如果不是,進入shouldParkAfterFailedAcquire(詳情見後面方法分析)方法主要作用是判斷自己的前置節點是否是SIGNAL狀態,是的話自己就可以阻塞自己了,調用parkAndCheckInterrupt(詳情見後面方法分析)方法,直到被喚醒或者中斷。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 獲取前一個節點
                final Node p = node.predecessor();
                // p是頭結點,獲取鎖成功
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }

                // 找到前節點爲signal,然後阻塞自己
                // 清理等待超時或者中斷的節點
                // 嘗試設置線程的狀態爲signal
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 出現異常
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire方法,這個方法主要做三件事,判斷自己的前置節點是否是SIGNAL,返回true,就可以阻塞了,不是如果狀態大於0,證明前面的節點被中斷或者超時了,需要從隊列清理了,不是大於0,就利用cas設置前置節點爲SIGNAL,返回false。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node 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.
             */
            // 如果pred節點釋放了狀態,會通知自己
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                // 大於0證明,前面的線程等待超時或者已經被中斷,需要從節點中移除
                // 需要找到不大於0的那個節點
                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.
             */
            // 找到小於等於0的前節點,設置爲SIGNAL
            // 這個地方ws值只會爲PROPAGATE或者0
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt方法,這個方法需要前面的方法返回true纔會執行,它會阻塞node的這個線程,返回線程的中斷中斷狀態並清理Thread.interrupted(),所以獨佔式獲取同步狀態對中斷不響應的。

private final boolean parkAndCheckInterrupt() {
        // 阻塞線程
        LockSupport.park(this);
        return Thread.interrupted();
    }

cancelAcquire方法,在finally塊裏面,出現異常就會執行這個方法,做一些處理當前node的操作。

// 異常後,finally裏面執行的方法
    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.
        // node是爲節點,設置尾節點是pred
        if (node == tail && compareAndSetTail(node, pred)) {
            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;
            // 不是頭結點和尾節點,前節點是SIGNAL
            if (pred != head &&
                    ((ws = pred.waitStatus) == Node.SIGNAL ||
                            (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                    pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }

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

附上一張acquire獨佔式獲取同步狀態的流程圖:
AbstractQueuedSynchroizer(AQS) 同步器詳解詳解

②、release()獨佔式釋放同步狀態,釋放線程後喚醒節點。
源代碼:
其中tryRelease也需要同步組件自己去實現,語義也被AQS所定義,true代表釋放成功,false代表失敗,如果爲true,就需要決定是否去喚醒節點,首先獲取同步隊列的頭節點,判斷頭結點不是空,證明有同步對別有節點才需要喚醒,判斷頭結點不是剛剛初始化,如果是剛剛初始化,就還沒有阻塞,請參考acquire的acquireQueued處理節點的邏輯,都爲true執行unparkSuccessor方法,false返回。

 public final boolean release(int arg) {
        // 釋放鎖
        if (tryRelease(arg)) {
            // 獲取頭結點
            Node h = head;
            // 頭結點不爲空,證明初始化了
            // 證明頭結點不是剛剛創建
            // 那就可以去喚醒頭結點或者它的後繼節點
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

unparkSuccessor()方法,喚醒節點,喚醒的node的後置節點,因爲在獲取同步狀態是我們阻塞的也是後置節點,喚醒後置節點後,會去找到前節點,也就是當前的頭結點去獲取同步狀態。

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)
            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.
         */
        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);
    }

獨佔式釋放同步狀態的流程圖:
AbstractQueuedSynchroizer(AQS) 同步器詳解詳解
③、acquireInterruptibly()響應中斷的獨佔式獲取同步狀態
可以看出如果線程中斷立馬返回異常,然後再去執行tryAcquire()獲取同步狀態,獲取失敗執行doAcquireInterruptibly方法。

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

doAcquireInterruptibly()方法,和acquire的acquireQueued的方法差不多,區別就是在parkAndCheckInterrupt這個方法如果返回true,就會拋異常InterruptedException,說明這個方法響應異常。

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())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

④、tryAcquireNanos()帶時間的獲取同步狀態,在時間內獲取到,返回true,超時返回false,首先判斷線程中斷狀態,爲true就拋異常,爲false就嘗試獲取同步狀態tryAcquire,獲取失敗執行doAcquireNanos方法。

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
                doAcquireNanos(arg, nanosTimeout);
    }

doAcquireNanos()方法,其實這個都是在acquire方法上的改進,我們看看這個方法,首先算下時間也就是deadline,然後加入同步隊列addWaiter方法,然後判斷node的前節點是否爲頭結點,是就嘗試獲取同步狀態,都爲true就返回,爲false就接着算下時間,判斷node前節點是否爲SIGNAL,也就是shouldParkAfterFailedAcquire這個方法,爲true,線程阻塞計算的時間,然後true(等待阻塞時間到)和false都判斷線程中斷狀態,中斷就拋出異常,執行異常方法,不爲true,繼續循環,直到獲取鎖或者超時。

 private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        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)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

2)、共享系列的方法
①、acquireShared共享式獲取同步狀態,獲取失敗就加入同步隊列
AQS也把語義指定好了,返貨負數證明沒有了,就執行doAcquireShared方法

 public final void acquireShared(int arg) {
        // 返回負數就證明沒有鎖了,加入同步隊列
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

doAcquireShared()方法,首先構造節點加入隊列addWaiter,然後獲取node的前節點,判斷node的前節點是否爲頭結點,如果是,獲取資源的個數,如果資源大於等於0,調用setHeadAndPropagate方法,然後返回,不滿足,調用shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法和獨佔式一樣。

private void doAcquireShared(int arg) {
        // 構建共享節點
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 獲取node的前節點
                final Node p = node.predecessor();
                // 前節點是否是頭節點
                if (p == head) {
                    // 獲取鎖的個數
                    int r = tryAcquireShared(arg);
                    // 大於等於0,獲取鎖成功
                    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);
        }
    }

setHeadAndPropagate()方法,設置頭結點,設置waitStatus爲Propagate,爲如果還有資源,喚醒後面的節點,調用doReleaseShared方法(這個方法會在共享式釋放同步狀態詳解)

private void setHeadAndPropagate(Node node, int propagate) {
        // 頭結點
        Node h = head; // Record old head for check below
        // 設置頭結點爲node
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        // 還有資源
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
                (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            // 當前節點的next節點是共享或者沒有next節點
            if (s == null || s.isShared())
                // 喚醒後置節點
                doReleaseShared();
        }
    }

②、releaseShared()共享式釋放狀態
tryReleaseShared是需要同步組件自己去實現,釋放成功調用doReleaseShared喚醒節點

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

doReleaseShared()方法,方法有些複雜,不好理解,我們主要來分析三個if的含義,第一個 if (ws == Node.SIGNAL) 表示當前node需要被喚醒,然後後面利用cas設置waitStatus爲0,因爲是共享模式可能有多個線程同時來釋放同步狀態,所以只能有一個釋放成功,另外一個重試;第二個else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)),其實也是用來處理併發的,當第一次併發失敗的線程第二次進入時,可能會看到ws等於0(因爲成功的線程設置的),所以利用cas設置爲PROPAGATE,表示傳遞,這裏補充一下不管是0或者PROPAGATE,都會被喚醒的線程利用cas設置爲SIGNAL(參考shouldParkAfterFailedAcquire方法);第三個(h == head)

  1. head.waitStatus的初始值必然爲SIGNAL,因此在併發時,必然只有一個線程A能將等待狀態由 SIGNAL CAS更新爲 0,
    該線程A會喚醒其他線程B
  2. 被喚醒的線程B會首先執行setHead
    因此如果最後h!=head,說明新一輪的喚醒競爭已經開始,當前線程c已經覺察到,因此繼續參與競爭,加快喚醒
    因此如果最後h==head,說明新一輪的喚醒競爭尚未開始,而被喚醒的線程B必然會開啓新一輪的喚醒競爭,而當前線程c可以安心退出喚醒競選

    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        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);
                }
                // 如果ws等於0,嘗試把cas設置waitStatus爲PROPAGATE,傳遞下去
                // 請聯繫shouldParkAfterFailedAcquire方法一起看
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //如果頭結點沒有發生變化,表示設置完成,退出循環
            // 如果發生變化,加入喚醒的過程(加速喚醒,可能存在多個線程在喚醒這些node,速度比一個接一個要快)
            if (h == head)                   // loop if head changed
                break;
        }
    }

    ③、acquireSharedInterruptibly()和tryAcquireSharedNanos()一個響應中斷,一個響應中斷支持添加獲取的超時時間(參考獨佔模式的這些方法)
    3)、ConditionObject系列方法
    ①、await()方法,類似Object的await方法,阻塞線程釋放鎖。
    我們可以看見await的第一步是調用addConditionWaiter方法,它的作用是構建等待節點加入隊列的尾部,使用的也是AQS的Node,隊列裏面順便也會清理清除Node不爲CONDITION的節點;第二步需要釋放線程獲取的同步狀態fullyRelease方法;第三步:阻塞線程,找到線程中斷時機,也就是調用signal方法的前後順序;第四步:調用acquireQueued方法處理節點(阻塞還是其它);第五步:清理節點unlinkCancelledWaiters方法(清除Node不爲CONDITION的節點);第六步:響應await語義,await阻塞線程時調用interrupt方法會拋異常reportInterruptAfterWait方法。

    public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 加入等待隊列,清除節點
            Node node = addConditionWaiter();
            // 釋放狀態
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                // 阻塞線程
                LockSupport.park(this);
                // 線程是被中斷喚醒的
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 加入同步隊列
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                // 清理節點
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                // 響應中斷  await語義
                reportInterruptAfterWait(interruptMode);
        }

    addConditionWaiter方法每次都是加入隊列的尾部

    private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
                private void unlinkCancelledWaiters() {
            // 獲取第一個
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                // 獲取第一個的下一個
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    // t需要斷開連接
                    t.nextWaiter = null;
                    // 第一次trail = null
                    // firstWaiter = next
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

    isOnSyncQueue方法判斷node是否在同步隊列中

    final boolean isOnSyncQueue(Node node) {
        // 節點狀態爲CONDITION ,或者node.prev == null 等待節點沒有前置節點
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 等待節點沒有next節點
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        // 循環查找
        return findNodeFromTail(node);

    checkInterruptWhileWaiting 方法,判斷是否是中斷喚醒,這方法就是爲了確認中斷的時機是在signal的前面還是後面signal,因爲需要響應中斷

    private int checkInterruptWhileWaiting(Node node) {
            // 判斷是否是線程中斷喚醒
            return Thread.interrupted() ?
                    (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                    0;
        }
                final boolean transferAfterCancelledWait(Node node) {
        // 設置成功表示在signal 執行之前
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
        // 設置成功表示在signal 執行之後
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

    acquireQueued和unlinkCancelledWaiters方法前面都介紹過了,一個是加入同步隊列,一個是清理節點,介紹下reportInterruptAfterWait方法,它是我爲了響應線程Interrupt方法,interruptMode == THROW_IE只在在signal方法後調用Interrupt方法才滿足,線程阻塞時調用Interrupt方法會拋異常,這是Object.await裏面滿足的,請參考checkInterruptWhileWaiting方法裏面的transferAfterCancelledWait方法理解其實現。

    private void reportInterruptAfterWait(int interruptMode)
                throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

    ②、awaitNanos(long nanosTimeout)和awaitUntil(Date deadline)都是提供了超時時間,和await方法類似,只是加入了時間機制。
    ③、awaitUninterruptibly不響應中斷方法,發現裏面都沒有判斷是都發生中斷的標記,只有調用signal喚醒node,循環纔會結束,然後調用acquireQueued處理這個節點(阻塞還是其它)

    public final void awaitUninterruptibly() {
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            boolean interrupted = false;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if (Thread.interrupted())
                    interrupted = true;
            }
            if (acquireQueued(node, savedState) || interrupted)
                selfInterrupt();
        }

    ④、signal方法,喚醒第一個等待隊列的node。

    public final void signal() {
            // 判斷是否獲取鎖
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 初始化
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

    doSignal方法,首先把這個first節點和等待隊列斷開連接,然後把調用transferForSignal方法把節點從等待隊列加入同步隊列,喚醒節點的線程,然後被喚醒的線程就會在await方法裏面執行acquireQueued這個方法。

    private void doSignal(Node first) {
            do {
                // 隊列裏面即將沒有節點,所以首尾都要爲null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 把first 斷開連接
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                    (first = firstWaiter) != null);
        }
                final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
    
        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

    ⑤、signalAll喚醒所有等待隊列的節點加入同步隊列,並且清空等待隊列

    public final void signalAll() {
            // 判斷是否獲取鎖
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 獲取第一個節點
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
                private void doSignalAll(Node first) {
            // 隊列設置爲null
            lastWaiter = firstWaiter = null;
            // 從首節點開始加入同步隊列,知道隊列爲空
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

    四、總結

    我們學習AQS其實我覺得主要從三個方面,也就是本文的第三部分,從獨佔式獲取和釋放同步狀態、共享式獲取和釋放同步狀態和ConditionObject裏面的等待/通知機制;這裏在說一下獨佔式釋放鎖和共享式釋放鎖,獨佔式因爲只會有一個線程獲取同步狀態,所以釋放時也只會有一個,但是在共享這一塊,我們在釋放同步同步狀態時可能會有多個線程同時來釋放,可能出現併發的情況,理解doReleaseShared是理解共享式釋放的重點;學習獲取和釋放同步狀態,理解同步隊列節點的變化是重點;學習等待/通知理解等待隊列和同步隊列的關係和節點的轉換;只有學習好了AQS才能更好的學習後面JUC的那些鎖。
    最後感慨下AQS裏面的邏輯是真心有些繞,本人有些理解的可能有些不夠。
    參考《Java 併發編程的藝術》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章