Java併發——阻塞佇列集(下)

午夜12點發表於2018-08-21

SynchronousQueue

接著上集繼續,SynchronousQueue是一個不儲存元素的阻塞佇列。每一個put操作必須等待一個take操作,否則不能繼續新增元素,所以其peek()方法始終返回null,沒有資料快取空間。SynchronousQueue支援公平與非公平訪問,預設採用非公平性策略訪問佇列。

構造方法


    public SynchronousQueue() {
        this(false);
    }
    
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue() : new TransferStack();
    }
複製程式碼

相對於ArrayBlockingQueue利用ReentrantLock實現公平與非公平,而SynchronousQueue利用TransferQueue、TransferStack實現公平與非公平,從命名上來看前者佇列,後者棧,SynchronousQueue的入隊、出隊操作都是基於transfer來實現,ctrl+alt+h檢視方法呼叫

Java併發——阻塞佇列集(下)

TransferQueue

TransferQueue內部定義如下


    // 頭節點
    transient volatile QNode head;
    // 尾節點
    transient volatile QNode tail;
    // 指向一個可能還未出隊被取消的節點,因為它在被取消時是最後一個插入節點
    transient volatile QNode cleanMe;

    // 預設建構函式,建立一個假節點
    TransferQueue() {
        QNode h = new QNode(null, false); // initialize to dummy node.
        head = h;
        tail = h;
    }
    
    static final class QNode {
        // 後繼節點
        volatile QNode next;
        // item資料
        volatile Object item;
        // 用來控制阻塞或喚醒
        volatile Thread waiter;       // to control park/unpark
        // 是否是生產者
        final boolean isData;

        QNode(Object item, boolean isData) {
            this.item = item;
            this.isData = isData;
        }
        ...
    }    
    ...
複製程式碼

公平策略


        E transfer(E e, boolean timed, long nanos) {
            QNode s = null; // constructed/reused as needed
            // 判斷是否是生產者,true為生產者,false為消費者
            boolean isData = (e != null);
            // 死迴圈
            for (;;) {
                // 獲取尾節點
                QNode t = tail;
                // 獲取頭節點
                QNode h = head;
                // 若尾節點或尾節點為空則跳出本次循序
                if (t == null || h == null)         // saw uninitialized value
                    continue;                       // spin
                // 若TransferQueue為空或當前節點與尾節點模式一樣
                if (h == t || t.isData == isData) { // empty or same-mode
                    QNode tn = t.next;
                    // 若t不是尾節點表明已有其他執行緒操作過,跳出本次迴圈重新來
                    if (t != tail)                  // inconsistent read
                        continue;
                    // 若之前獲取的尾節點後繼不為空表明已有其他執行緒新增過節點
                    if (tn != null) {               // lagging tail
                        // CAS將tn置為尾節點
                        advanceTail(t, tn);
                        continue;
                    }
                    // 若採用了時限模式且超時,直接返回null
                    if (timed && nanos <= 0)        // can't wait
                        return null;
                    // 若s為null,構建一個新節點    
                    if (s == null)
                        s = new QNode(e, isData);
                    // CAS將新節點加入佇列中,若失敗重新來
                    if (!t.casNext(null, s))        // failed to link in
                        continue;
                    // CAS將新節點s置為尾節點
                    advanceTail(t, s);              // swing tail and wait
                    // 自旋獲取匹配item
                    Object x = awaitFulfill(s, e, timed, nanos);
                    // 若x==s表明執行緒獲取匹配項時,超時或者被中斷,清除節點s
                    if (x == s) {                   // wait was cancelled
                        clean(t, s);
                        return null;
                    }
                    // 判斷節點s是否已經出隊
                    if (!s.isOffList()) {           // not already unlinked
                        // CAS將節點s置為head,移出佇列
                        advanceHead(t, s);          // unlink if head
                        if (x != null)              // and forget fields
                            s.item = s;
                        s.waiter = null;
                    }
                    return (x != null) ? (E)x : e;
                }
                // else分支下述
            }
        }
複製程式碼

我們假定有執行緒A、B在put操作,執行緒C在take操作,當前TransferQueue初始化如下:

Java併發——阻塞佇列集(下)
執行緒A新增元素A,head=tail走第一個分支,因為沒有采用鎖機制,所以可能會有其他執行緒搶先操作,其採用各種判斷以及CAS來判斷是否有其他執行緒操作過,新增完尾結點後,會呼叫awaitFulfill方法,其作用是自旋尋找匹配節點,若超過自旋次數此執行緒會阻塞,執行緒被中斷或採用時限模式時獲取超時此次操作會被取消。


        Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
            // 獲取最後期限
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            // 獲取當前執行緒
            Thread w = Thread.currentThread();
            // 獲取自旋次數,若新節點s為頭節點後繼節點才能自旋
            int spins = ((head.next == s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (;;) {
                // 判斷當前執行緒是否被中斷
                if (w.isInterrupted())
                    // 取消當前節點,cas將item置為this
                    s.tryCancel(e);
                // 獲取節點s的item
                Object x = s.item;
                // 若執行緒中斷,節點s的item與x會不相等,直接返回x
                if (x != e)
                    return x;
                // 若採用了時限模式
                if (timed) {
                    // 計算剩餘時間
                    nanos = deadline - System.nanoTime();
                    // 若超時,取消節點
                    if (nanos <= 0L) {
                        s.tryCancel(e);
                        continue;
                    }
                }
                // 若還有自旋次數,自旋-1
                if (spins > 0)
                    --spins;
                // 若等待執行緒為null,將節點s的等待執行緒置為當前執行緒
                else if (s.waiter == null)
                    s.waiter = w;
                // 若沒有采用時限模式則呼叫LockSupport.park()直接阻塞執行緒
                else if (!timed)
                    LockSupport.park(this);
                // 若剩餘時間超過自旋時間閾值則指定時間阻塞
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }
        
        void tryCancel(Object cmp) {
                UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
        }
複製程式碼

從原始碼中可以看到只有頭節點後繼才能自旋,執行緒A自旋一段時間匹配節點,若自旋次數用光會一直阻塞,所以每一個執行緒只有匹配到節點後或者因超時、中斷被取消才能繼續新增元素

執行緒A自旋,執行緒B接著put

Java併發——阻塞佇列集(下)

那麼什麼時候才匹配到呢?在開頭我們提到每一個put操作必須等待一個take操作,這時其他執行緒take(),e為null,isData為false,與尾節點的isData屬性不同,走進else分支,先獲取頭節點的後繼節點資料,若沒有其他執行緒搶先操作,且put操作未被取消,m.casItem(x, e)資料替換,將節點m的item屬性置為null,若CAS替換成功表明匹配成功,在put自旋時會用item與e比較,take()將item置為null,不相等返回null


                else {                            // complementary-mode
                    // 獲取頭節點後繼
                    QNode m = h.next;               // node to fulfill
                    // 若t不是尾節點或者m為null或者h不是頭節點,即已有其他執行緒搶先操作過
                    if (t != tail || m == null || h != head)
                        continue;                   // inconsistent read
                     
                    Object x = m.item;
                    if (isData == (x != null) ||    // 節點已被操作過
                        x == m ||                   // 節點被取消
                        !m.casItem(x, e)) {         // lost CAS
                        // CAS將m置為頭節點,重來
                        advanceHead(h, m);          // dequeue and retry
                        continue;
                    }
                    // 若走這裡,表明匹配成功
                    advanceHead(h, m);              // successfully fulfilled
                    // 喚醒m的等待執行緒
                    LockSupport.unpark(m.waiter);
                    return (x != null) ? (E)x : e;
                }
複製程式碼

TransferStack

TransferStack內部定義如下


    // 未執行的消費者
    static final int REQUEST    = 0;
    // 未執行的生產者
    static final int DATA       = 1;
    // 執行緒正在匹配節點
    static final int FULFILLING = 2;
    volatile SNode head;
    
    static final class SNode {
        volatile SNode next;        // next node in stack
        volatile SNode match;       // the node matched to this
        volatile Thread waiter;     // to control park/unpark
        Object item;                // data; or null for REQUESTs
        int mode;
        ...
    }
    ...
複製程式碼

TransferStack相對於TransferQueue中的節點,其資料項item與模式mode不需要用volatile修飾,因為它們總是寫在前讀在後

非公平模式


        E transfer(E e, boolean timed, long nanos) {
            SNode s = null; // constructed/reused as needed
            // REQUEST:消費者;DATA:生產者
            int mode = (e == null) ? REQUEST : DATA;
            for (;;) {
                SNode h = head;
                // 若棧為空或者新增元素模式與首元素模式相同
                if (h == null || h.mode == mode) {  // empty or same-mode
                    // 超時
                    if (timed && nanos <= 0) {      // can't wait
                        // 若節點被取消,將取消節點出隊,重新來
                        if (h != null && h.isCancelled())
                            casHead(h, h.next);     // pop cancelled node
                        else
                            return null;
                    //若不採用限時或者未超時,建立節點CAS將其置為頭節點,s→h     
                    } else if (casHead(h, s = snode(s, e, h, mode))) {
                        // 自旋匹配
                        SNode m = awaitFulfill(s, timed, nanos);
                        // 若m==s表明節點被取消
                        if (m == s) {               // wait was cancelled
                            clean(s);
                            return null;
                        }
                        if ((h = head) != null && h.next == s)
                            casHead(h, s.next);     // help s's fulfiller
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    }
                // 其餘分支下述    
            }
        }
複製程式碼

依然模擬場景,假定現線上程A、B在put,執行緒C、D在take。
執行緒A進行put新增元素A,CAS頭插元素A,呼叫awaitFulfill()自旋匹配,注意只有頭節點、空棧或者協助節點才能自旋,每次自旋都會進行條件判斷,為了


        SNode awaitFulfill(SNode s, boolean timed, long nanos) {
            // 最後期限
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Thread w = Thread.currentThread();
            // 自旋次數
            // 若棧為空、節點為首結點或者該節點模式為FULFILLING才能自旋
            int spins = (shouldSpin(s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (;;) {
                // 若執行緒中斷,取消該節點
                if (w.isInterrupted())
                    s.tryCancel();
                // 匹配節點
                SNode m = s.match;
                if (m != null)
                    return m;
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    // 超時,取消節點
                    if (nanos <= 0L) {
                        s.tryCancel();
                        continue;
                    }
                }
                // 每次自旋需先判斷是否滿足自旋條件,滿足次數-1
                if (spins > 0)
                    spins = shouldSpin(s) ? (spins-1) : 0;
                else if (s.waiter == null)
                    s.waiter = w; // establish waiter so can park next iter
                 // 若沒有采用時限模式則呼叫LockSupport.park()直接阻塞執行緒
                else if (!timed)
                    LockSupport.park(this);
                // 若剩餘時間超過自旋時間閾值則指定時間阻塞    
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }
複製程式碼

執行緒B接著put元素B,頭節點A的模式與put操作的模式一致,CAS頭插成功後,也呼叫awaitFulfill()自旋,由於頭節點變為執行緒B所以只有執行緒B才能自旋匹配,這也是不公平的體現

Java併發——阻塞佇列集(下)
節點的取消與公平模式的差不多都是將屬性置為其本身


        void tryCancel() {
                UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
            }
複製程式碼

這時執行緒C進行take操作,take的模式(REQUEST)明顯與當前頭節點B(DATA)不一致且頭節點模式也不為FULFILLING,所以transfer走入else if分支。


                // 若頭節點的模式不為 FULFILLING
                } else if (!isFulfilling(h.mode)) { // try to fulfill
                    // 若頭節點被取消,將頭節點出隊重新來
                    if (h.isCancelled())            // already cancelled
                        casHead(h, h.next);         // pop and retry
                    else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                        for (;;) { // loop until matched or waiters disappear
                            SNode m = s.next;       // m is s's match
                            if (m == null) {        // all waiters are gone
                                // 將節點s出隊
                                casHead(s, null);   // pop fulfill node
                                s = null;           // use new node next time
                                break;              // restart main loop
                            }
                            // 獲取節點m的後繼節點
                            SNode mn = m.next;
                            // 嘗試匹配
                            if (m.tryMatch(s)) {
                                // 匹配成功,將節點s、m出隊
                                casHead(s, mn);     // pop both s and m
                                return (E) ((mode == REQUEST) ? m.item : s.item);
                            } else                  // lost match
                                // 若匹配失敗,將m出隊
                                s.casNext(m, mn);   // help unlink
                        }
                    }
複製程式碼

建立一個FULFILLING模式的節點並CAS將其置為頭節點,與其後繼匹配,匹配方法如下


            boolean tryMatch(SNode s) {
                if (match == null &&
                    UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
                    Thread w = waiter;
                    if (w != null) {    // waiters need at most one unpark
                        waiter = null;
                        LockSupport.unpark(w);
                    }
                    return true;
                }
                return match == s;
            }
複製程式碼

若節點沒有被取消,其match為null,被取消則為其自身。成功匹配後將一對put、take操作的節點出隊。我們假定另一種場景,若執行緒C的take節點入隊後,未進行匹配執行緒D中途take

Java併發——阻塞佇列集(下)
頭節點C模式為FULFILLING,transfer走入最後一個分支,並不會先建立節點而是幫助頭節點先行匹配完成入隊出隊操作後,再第二次迴圈繼續執行自己操作


                // 頭節點模式為 FULFILLING
                } else {                            // help a fulfiller
                    SNode m = h.next;               // m is h's match
                    if (m == null)                  // waiter is gone
                        casHead(h, null);           // pop fulfilling node
                    else {
                        SNode mn = m.next;
                        if (m.tryMatch(h))          // help match
                            casHead(h, mn);         // pop both h and m
                        else                        // lost match
                            h.casNext(m, mn);       // help unlink
                    }
                }
複製程式碼

LinkedTransferQueue

LinkedTransferQueue是由連結串列結構組成的無界阻塞FIFO佇列

主要欄位


    // 判斷是否多核處理器
    private static final boolean MP =
        Runtime.getRuntime().availableProcessors() > 1;
    // 自旋次數
    private static final int FRONT_SPINS   = 1 << 7;
    // 前驅節點正在操作,當前節點自旋的次數
    private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;

    static final int SWEEP_THRESHOLD = 32;
    // 頭節點
    transient volatile Node head;
    // 尾節點
    private transient volatile Node tail;
    // 刪除節點失敗的次數
    private transient volatile int sweepVotes;
    
    /**
     * xfer()方法中使用
     */
    private static final int NOW   = 0; // for untimed poll, tryTransfer
    private static final int ASYNC = 1; // for offer, put, add
    private static final int SYNC  = 2; // for transfer, take
    private static final int TIMED = 3; // for timed poll, tryTransfer
複製程式碼

Node內部類


    static final class Node {
        final boolean isData;   // false if this is a request node
        volatile Object item;   // initially non-null if isData; CASed to match
        volatile Node next;
        volatile Thread waiter; 
        Node(Object item, boolean isData) {
            UNSAFE.putObject(this, itemOffset, item); // relaxed write
            this.isData = isData;
        }
        ...
    }    
複製程式碼

是不是感覺與SynchronousQueue中TransferQueue的QNode節點類定義很類似

xfer

LinkedTransferQueue的大多方法都是基於xfer()方法


    /**
     * @param e 入隊資料
     * @param haveData true:入隊;flase:出隊
     * @param how NOW, ASYNC, SYNC, or TIMED
     * @param nanos 期限僅TIMED限時模式使用
     */
    private E xfer(E e, boolean haveData, int how, long nanos) {
        // 若是入隊操作,但無資料拋異常
        if (haveData && (e == null))
            throw new NullPointerException();
        Node s = null;                        // the node to append, if needed
        retry:
        for (;;) {                            // restart on append race
            // 從頭節點遍歷
            for (Node h = head, p = h; p != null;) { // find & match first node
                // 獲取模式isData
                boolean isData = p.isData;
                // 獲取資料項
                Object item = p.item;
                // 找到未匹配的節點
                if (item != p && (item != null) == isData) { // unmatched
                    // 若操作模式一樣,不匹配
                    if (isData == haveData)   // can't match
                        break;
                    // 若匹配,CAS將替換item
                    if (p.casItem(item, e)) { // match
                        for (Node q = p; q != h;) {
                            Node n = q.next;  // update by 2 unless singleton
                            // 更新 head
                            if (head == h && casHead(h, n == null ? q : n)) {
                                h.forgetNext();
                                break;
                            }                 // advance and retry
                            if ((h = head)   == null ||
                                (q = h.next) == null || !q.isMatched())
                                break;        // unless slack < 2
                        }
                        // 喚醒執行緒
                        LockSupport.unpark(p.waiter);
                        return LinkedTransferQueue.cast(item);
                    }
                }
                // 後繼
                Node n = p.next;
                // 若p的後繼是其自身,表明p已經有其他執行緒操作過,從頭節點重寫開始
                p = (p != n) ? n : (h = head); // Use head if p offlist
            }
            // 若沒有找到匹配節點,
            // NOW為untimed poll, tryTransfer,不會入隊
            if (how != NOW) {                 // No matches available
                if (s == null)
                    // 建立節點
                    s = new Node(e, haveData);
                // 尾插入隊    
                Node pred = tryAppend(s, haveData);
                if (pred == null)
                    continue retry;           // lost race vs opposite mode
                // 若不是非同步操作    
                if (how != ASYNC)
                    // 阻塞等待匹配值
                    return awaitMatch(s, pred, e, (how == TIMED), nanos);
            }
            return e; // not waiting
        }
    }
複製程式碼

以put()方法為例,假定佇列為空此時有執行緒put(其內部xfer(e, true, ASYNC, 0)),因為不等於now,呼叫tryAppend()方法尾插入隊


    private Node tryAppend(Node s, boolean haveData) {
        // 從尾節點開始
        for (Node t = tail, p = t;;) {        // move p to last node and append
            Node n, u;                        // temps for reads of next & tail
            // 若佇列為空CAS將S置為頭節點
            if (p == null && (p = head) == null) {
                if (casHead(null, s))
                    return s;                 // initialize
            }
            else if (p.cannotPrecede(haveData))
                return null;                  // lost race vs opposite mode
            // 若不是最後節點    
            else if ((n = p.next) != null)    // not last; keep traversing
                p = p != t && t != (u = tail) ? (t = u) : // stale tail
                    (p != n) ? n : null;      // restart if off list
            // CAS設定將s置為p的後繼
            else if (!p.casNext(null, s))
                // 若設定失敗重新來
                p = p.next;                   // re-read on CAS failure
            else {
                if (p != t) {                 // update if slack now >= 2
                    while ((tail != t || !casTail(t, s)) &&
                           (t = tail)   != null &&
                           (s = t.next) != null && // advance and retry
                           (s = s.next) != null && s != t);
                }
                return p;
            }
        }
    }
複製程式碼

從原始碼中可以得知,當第一次tryAppend()佇列為空時只設定了頭節點,第二次tryAppend()才會設定尾結點,入隊後,若不是ASYNC還會呼叫awaitMatch()方法阻塞匹配


    private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
        // 若限時獲取最後期限
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        Thread w = Thread.currentThread();
        int spins = -1; // initialized after first item and cancel checks
        ThreadLocalRandom randomYields = null; // bound if needed
         
        for (;;) {
            Object item = s.item;
            // 不相等表明已經匹配過,有其他執行緒已操作過
            if (item != e) {                  // matched
                // assert item != s;
                // 取消節點
                s.forgetContents();           // avoid garbage
                return LinkedTransferQueue.cast(item);
            }
            // 若執行緒中斷或超時則取消節點
            if ((w.isInterrupted() || (timed && nanos <= 0)) &&
                    s.casItem(e, s)) {        // cancel
                unsplice(pred, s);
                return e;
            }
            // 初始化自旋次數 
            if (spins < 0) {                  // establish spins at/near front
                if ((spins = spinsFor(pred, s.isData)) > 0)
                    randomYields = ThreadLocalRandom.current();
            }
            // 自旋
            else if (spins > 0) {             // spin
                --spins;
                if (randomYields.nextInt(CHAINED_SPINS) == 0)
                    Thread.yield();           // occasionally yield
            }
            else if (s.waiter == null) {
                s.waiter = w;                 // request unpark then recheck
            }
            // 若採用限時則限時阻塞
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos > 0L)
                    LockSupport.parkNanos(this, nanos);
            }
            // 直接阻塞
            else {
                LockSupport.park(this);
            }
        }
    }
複製程式碼

其整個佇列只存在一個操作(入隊或出隊),若不同操作會替換item喚醒相應另個執行緒,若相同操作則根據形參how判斷判斷
NOW:直接返回操作節點不入隊
ASYNC:操作節點尾插入隊,但不會阻塞等待直接返回,同一個執行緒隨即可以接著操作
SYNC:操作節點尾插入隊且會自旋匹配一段時間,自旋次數用完進入阻塞狀態,像SynchronousQueue一樣同一個執行緒操作完必須匹配到或被取消後才能繼續操作
TIMED:限時模式,在指定時間內若沒匹配到操作會被取消

相對於SynchronousQueue,LinkedTransferQueue可以儲存元素且可支援不阻塞形式的操作,而相對於LinkedBlockingQueue維護了入隊鎖和出隊鎖,LinkedTransferQueue通過CAS保證執行緒安全更提高了效率

LinkedBlockingDeque

LinkedBlockingDeque是一個由連結串列結構組成的雙向阻塞佇列,雙向佇列就意味著可以從對頭、對尾兩端插入和移除元素。LinkedBlockingDeque預設構造容量Integer.MAX_VALUE,也可以指定容量

主要屬性


    // 頭節點
    transient Node first;
    // 尾節點
    transient Node last;
    // 元素個數
    private transient int count;
    // 容量
    private final int capacity;
    
    final ReentrantLock lock = new ReentrantLock();
    
    private final Condition notEmpty = lock.newCondition();
    
    private final Condition notFull = lock.newCondition();
複製程式碼

Node內部類


    static final class Node {
        // 資料項
        E item;
        // 前驅節點
        Node prev;
        // 後繼節點
        Node next;
        
        Node(E x) {
            item = x;
        }
    }
複製程式碼

入隊

  • 頭插
  • 
        public void putFirst(E e) throws InterruptedException {
            // 判空
            if (e == null) throw new NullPointerException();
            // 建立節點
            Node node = new Node(e);
            final ReentrantLock lock = this.lock;
            // 獲取鎖
            lock.lock();
            try {
                while (!linkFirst(node))
                    notFull.await();
            } finally {
                lock.unlock();
            }
        }
    複製程式碼
    判空處理然後獲取鎖,呼叫linkFirst()入隊
    
        private boolean linkFirst(Node node) {
            // assert lock.isHeldByCurrentThread();
            // 若當前元素個數超過指定容量,返回false
            if (count >= capacity)
                return false;
            // 獲取首節點    
            Node f = first;
            // 新節點後繼指向首節點
            node.next = f;
            // 新節點置為首節點
            first = node;
            // 若佇列為空則新節點置為尾節點
            if (last == null)
                last = node;
            // 若不為空,新節點置為首節點的前驅節點    
            else
                f.prev = node;
            // 元素個數+1    
            ++count;
            // 喚醒出隊(消費者)等待佇列中執行緒
            notEmpty.signal();
            return true;
        }
    複製程式碼
  • 尾插
  • 
        public void putLast(E e) throws InterruptedException {
            if (e == null) throw new NullPointerException();
            Node node = new Node(e);
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                while (!linkLast(node))
                    notFull.await();
            } finally {
                lock.unlock();
            }
        }
    複製程式碼
    判空處理然後獲取鎖,呼叫linkLast()入隊
    
        private boolean linkLast(Node node) {
            // assert lock.isHeldByCurrentThread();
            // 若當前元素個數超過指定容量,返回false
            if (count >= capacity)
                return false;
            // 獲取尾節點
            Node l = last;
            // 將新節點的前驅節點置為原尾節點
            node.prev = l;
            // 新節點置為尾節點
            last = node;
            // 若佇列為空,首結點置為頭節點
            if (first == null)
                first = node;
            // 否則將新節點置為原未節點的後繼節點
            else
                l.next = node;
            // 元素個數+1    
            ++count;
            // 喚醒出隊(消費者)等待佇列中執行緒
            notEmpty.signal();
            return true;
        }
    複製程式碼

    出隊

  • 頭出
  • 
        public E takeFirst() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                E x;
                while ( (x = unlinkFirst()) == null)
                    notEmpty.await();
                return x;
            } finally {
                lock.unlock();
            }
        }
    複製程式碼
    unlinkFirst()方法
    
        private E unlinkFirst() {
            // assert lock.isHeldByCurrentThread();
            // 獲取頭節點
            Node f = first;
            // 若first為null即佇列為空,返回null
            if (f == null)
                return null;
            // 獲取頭節點的後繼節點
            Node n = f.next;
            E item = f.item;
            // 刪除頭節點
            f.item = null;
            f.next = f; // help GC
            // 將原頭節點的後繼節點置為頭節點
            first = n;
            // 若原佇列僅一個節點,則尾節點置空
            if (n == null)
                last = null;
            // 否則原頭節點的後繼節點的前驅置為null
            else
                n.prev = null;
            // 元素個數-1    
            --count;
            // 喚醒入隊(生產者)等待佇列中執行緒
            notFull.signal();
            return item;
        }
    複製程式碼
  • 尾出
  • 
        public E takeLast() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                E x;
                while ( (x = unlinkLast()) == null)
                    notEmpty.await();
                return x;
            } finally {
                lock.unlock();
            }
        }
    複製程式碼
    unlinkLast
    
        private E unlinkLast() {
            // assert lock.isHeldByCurrentThread();
            // 獲取尾節點
            Node l = last;
            // 尾節點為null即佇列為空,返回null
            if (l == null)
                return null;
            // 獲取原尾節點的前驅節點    
            Node p = l.prev;
            E item = l.item;
            // 刪除尾節點
            l.item = null;
            l.prev = l; // help GC
            // 將原尾節點的前驅節點置為尾節點
            last = p;
            // 若原佇列僅一個節點,則頭節點置空
            if (p == null)
                first = null;
            // 否則原尾節點的前驅節點的後繼置為null    
            else
                p.next = null;
            // 元素個數-1    
            --count;
            notFull.signal();
            return item;
        }
    複製程式碼
    邏輯就不多說了,看過LinkedList原始碼的應該不會陌生,除了多了喚醒阻塞獲取鎖操作,基本邏輯類似

    總結

  • ArrayBlockingQueue
  • 陣列實現的有界FIFO阻塞佇列,初始化時必須指定容量。內部通過ReentrantLock(一把鎖)保證出入隊執行緒安全、支援公平與非公平,因為公平性通常會降低吞吐量所以預設非公平策略;putIndex、takeIndex屬性維護入隊、出隊位置;notEmpty、notFull兩個Condition佇列,利用Condition的等待喚醒機制實現可阻塞式的入隊和出隊

  • LinkedBlockingQueue
  • 連結串列實現的有界FIFO阻塞佇列,預設容量Integer.MAX_VALUE。內部通過takeLock、putLock兩把ReentrantLock鎖保證出入隊執行緒安全,兩個鎖降低執行緒由於執行緒無法獲取單個lock而進入WAITING狀態的可能性提高了執行緒併發執行的效率,也因此其count屬性用原子操作類(可能兩個執行緒一個出隊一個入隊同時操作count需要原子操作)。notEmpty、notFull兩個Condition佇列,利用Condition的等待喚醒機制實現可阻塞式的入隊和出隊

  • PriorityBlockingQueue
  • 支援優先順序的無界阻塞佇列(就像無限流量一樣內部還是定義了最大容量Integer.MAX_VALUE - 8),預設情況元素採取自然順序升序排序,但不能保證同優先順序元素的順序。因為無界其插入始終成功,所以內部只維護了一個notEmpty(出隊)Condition佇列。通過ReentrantLock以及CAS一同維護執行緒安全且儘可能地縮小了鎖的範圍以此減少鎖競爭提高效能,底層結構採用基於陣列的堆實現

  • DelayQueue
  • 支援優先順序、延時獲取元素的無界阻塞佇列,佇列使用PriorityQueue來實現。佇列中的元素必須實現Delayed介面,只有在延遲期滿時才能從佇列中提取元素。雖然PriorityQueue執行緒不安全,但在呼叫同時需要獲取ReentrantLock鎖,也是利用Condition的等待喚醒機制實現可阻塞式的入隊和出隊。屬性leader不為null時,表明有執行緒佔用其作用在於減少不必要的競爭

  • SynchronousQueue
  • 不儲存元素的阻塞佇列沒有資料快取空間,每個執行緒的put操作必須等待一個take操作,否則不能繼續新增元素。兩個內部類TransferQueue佇列、TransferStack棧實現公平與非公平策略,其大多方法基於transfer()方法實現

  • LinkedTransferQueue
  • 連結串列結構組成的無界阻塞FIFO佇列,核心方法xfer()。是SynchronousQueue和LinkedBlockingQueues的合體,相對於SynchronousQueue,LinkedTransferQueue可以儲存元素且可支援不阻塞形式的操作,而相對於LinkedBlockingQueue維護了入隊鎖和出隊鎖,LinkedTransferQueue通過CAS保證執行緒安全更提高了效率

  • LinkedBlockingDeque
  • LinkedBlockingDeque是一個由連結串列結構組成的雙向阻塞佇列,雙向佇列就意味著可以從對頭、對尾兩端插入和移除元素。預設構造容量Integer.MAX_VALUE,出隊入隊和LinkedList有點類似,LinkedBlockingDeque多了ReentrantLock鎖機制實現執行緒安全以及notEmpty、notFull兩個Condition佇列,利用Condition的等待喚醒機制實現可阻塞式的入隊和出隊

    感謝

    《java併發程式設計的藝術》

    相關文章