SynchronousQueue 學習筆記

介紹

SynchronounsBlockingQueue 是一種同步阻塞隊列,每當有線程操作添加元素時,添加線程會一直阻塞直到有其他線程獲取這個元素。SynchronounsBlockingQueue 不想其他的阻塞隊列有個數組或者鏈表的數據結構存儲元素,即阻塞隊列的長度一直是 0。元素直接從添加線程轉移到獲取線程,所以 SynchronounsBlockingQueue 儘管也是繼承自 Collection,但是迭代器遍歷不到任何元素。也是由於這個原因,定義的用於返回但是不刪除隊列元素的 peek()方法永遠都返回 Null。

SynchronounsBlockingQueue 實現了dual stack and dual queue算法,以比較好的性能實現了元素從一個線程移到另外一個線程上。SynchronounsBlockingQueue 基於該算法提供了公平和非公平策略供選擇使用。

原理

一般處理共享資源都是用鎖來保護以保證線程安全,但是是用鎖會增加額外的開銷。在高性能場景下,一般會盡量避免是用鎖。比如 StampedLock 會採用大量自旋和 CAS 修改的技術,最後纔是用鎖。

SynchronousQueue 也是採用類似的方式。SynchronousQueue 實現並擴展了dual stack and dual queue算法,構建了兩個底層數據結構,分別是用於實現非公平策略的棧和實現公平策略的隊列。這兩種策略模式性能表現上差距不大,通常情況下,公平策略下的線程的響應速度會高一些,而非公平策略能夠支持更多的線程數。

雖然說 SynchronousQueue 的隊列長度永遠是 0,但是實際上底層的棧或者隊列依然會存儲線程獲取元素或者添加元素的操作以及操作對應的元素。

SynchronousQueue 不管在什麼時候都處於一下三種狀態:

  1. date:線程操作入隊
  2. request:線程操作出隊或者底層隊列(或者棧)是空隊列
  3. fulfill:線程請求獲取元素並且此時隊列(或者棧)持有元素;線程操作入隊並且此時有線程等待獲取元素

SynchronousQueue 下的棧和隊列在概念上有非常多相似的地方,但是爲了能夠獨立向前棧和隊列是獨立實現的。SynchronousQueue 實現的算法細節和“dual stack and dual queue”論文中描述的算法有許多不一樣的地方,尤其是關於取消操作。

  1. 論文中的算法是基於 bit-marked pointers,SynchronousQueue 是基於單向列表節點的 bits 表示,目的是方便後續改進。
  2. SynchronousQueues 中入隊和出隊線程需要阻塞等待 fulfilled 狀態。
  3. 支持通過超時、線程中斷探測取消等方式清除節點。

SynchronousQueue 的線程阻塞通過自旋或者 LockSuport 的 park/unpark 方法實現。如果節點位於 fulfilled 節點的下一個位置,那麼通過自旋等待成爲 fulfilled,否則使用 LockSupport 的 park 方法,是線程阻塞。

SynchronousQueue 對於添加元素和獲取元素的操作,對棧和隊列抽象出一個公共方法:E transfer(E e, boolean timed, long nanos),底層公平策略基於隊列實現 transfer,非公平策略基於棧實現 transfer。

    /**
      * Transferer 是 dual stack 和 dual queue 算法抽象出來的父類
      * dual stacks 和 dual queues 有非常多相似的代碼,但是爲了能夠讓兩者分別先前演進,實現上完全分開。
      * 但是由於實現的目的或者提供的功能是類似的,所以抽象出一個公共父類。或許將來可以共同演進的代碼可以放到父類中。
      */
     abstract static class Transferer<E> {
         /**
          * 抽象出來的 put 和 take 操作。如果 e==null,那麼是 take 操作;否則是 put 操作
          *
          * @param e 元素。如果元素不存在,那麼是 request,否則是 date。
          * @param timed 操作是否需要超時
          * @param nanos 超時時間
          * @return 如果是 date 操作並且匹配到補足節點,那麼返回補足元素。如果超時返回 null;如果是 request 操作,返回匹配到的元素。
          */
         abstract E transfer(E e, boolean timed, long nanos);
     }

棧 transfer 操作邏輯

循環處理一下三個操作:

  1. 如果棧是空棧或者和本次操作的模式一致(date、request、fulfill),構建節點入棧。
  2. 如果棧正好能夠滿足操作(比如 date 請求,結果能夠匹配到一個 request),往棧中添加一個 fulfilled 節點,匹配到一個補足的節點,將這兩個節點 pop 出棧。由於其他線程正在做第三部操作,該匹配操作不一定能夠成功。
  3. 如果棧頂節點已經匹配到 fulfill 狀態的節點,那麼線程優先處理棧頂節點的匹配操作,將棧頂元素和棧頂元素和它的 fulfill 狀態的元素出棧,之後再處理自己的業務。

主要操作的 transfer 操作流程:
空隊列下的添加和獲取元素操作:
空隊列下take操作

空隊列下的獲取元素的操作:
空隊列獲取元素示意圖
代碼詳情如下:

    /**
     * dual stack 實現
     */
    static final class TransferStack<E> extends Transferer<E> {
    
         // 節點的模式。
         static final int REQUEST    = 0;
         static final int DATA       = 1;
         static final int FULFILLING = 2;
    
         // 判斷節點的狀態是否是 FULFILLING 模式
         static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }
    
         /**
          * 實現棧的底層數據結構
          */
         static final class SNode {
             // 下一個節點的引用
             volatile SNode next;
             // 補足節點
             volatile SNode match;
             // 操作請求的線程,方便其他線程喚醒
             volatile Thread waiter;
             // 元素。如果是 request 操作,元素時 null
             Object item;
             // 節點的模式
             int mode;
    
             SNode(Object item) {
                 this.item = item;
             }
    
             /*
              * CAS 更新節點的下一個節點引用,並返回是否更新成功
              */
             boolean casNext(SNode cmp, SNode val) {
                 return cmp == next &&
                     UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
             }
    
             /**
              * 匹配節點
              * CAS 將節點的 match 屬性引用從 null 改成 s。查看自身節點 waiter 是否有指向具體線程,如果有將線程喚醒。
              *
              * @param s 匹配到的節點
              * @return true 如果匹配到節點,那麼返回 true。
              */
             boolean tryMatch(SNode s) {
                 if (match == null &&
                     UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
                     Thread w = waiter;
                     // waiter = null 說明添加節點的線程已經使用 LockSupport.park 掛起了
                     if (w != null) {
                         waiter = null;
                         LockSupport.unpark(w);
                     }
                     return true;
                 }
                 return match == s;
             }
    
             /**
              * 取消節點
              * 將 match CAS 從 null 改成自身。
              */
             void tryCancel() {
                 UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
             }
    
             /**
              * 判斷是否是取消節點。比較 match 和自身是否是同一個節點。
              */
             boolean isCancelled() {
                 return match == this;
             }
         }
    
         // 堆頂元素
         volatile SNode head;
    
         // CAS 修改堆頂元素。從 h 改成 nh
         boolean casHead(SNode h, SNode nh) {
             return h == head &&
                 UNSAFE.compareAndSwapObject(this, headOffset, h, nh);
         }
    
         /**
          * 構建或者重建 SNode 節點。由於競爭失敗可能導致不在需要 SNode,所以延遲創建節點,直到需要用到的時候才創建,避免無需要的內存開銷。
          */
         static SNode snode(SNode s, Object e, SNode next, int mode) {
             if (s == null) s = new SNode(e);
             s.mode = mode;
             s.next = next;
             return s;
         }
    
         /**
          * 添加或者獲取元素。如果 e!=null 則是添加元素,否則是獲取元素
          */
         @SuppressWarnings("unchecked")
         E transfer(E e, boolean timed, long nanos) {
             // 延遲初始化
             SNode s = null;
             // 模式
             int mode = (e == null) ? REQUEST : DATA;
    
             for (;;) {
                 SNode h = head;
                 // 如果是空棧或者模式相同 那麼可能需要入隊
                 if (h == null || h.mode == mode) {
                     // 如果 timed == true 並且 nanos <= 0 說明操作不需要等待或者已經超時,可能需要廢棄節點
                     if (timed && nanos <= 0) {
                         // 如果棧頂非空並且是取消的節點,那麼棧頂出棧
                         if (h != null && h.isCancelled())
                             casHead(h, h.next);
                         // 空棧時 put(E e)會走到這,直接退出
                         else
                             return null;
                     // 空棧並且操作需要等待,那麼將棧頂從 null 改成新創建的節點
                     } else if (casHead(h, s = snode(s, e, h, mode))) {
                         // 等待其他元素匹配
                         SNode m = awaitFulfill(s, timed, nanos);
                         // 如果匹配到的元素時說明等待超時或者不需要等待直接退出
                         if (m == s) {
                             clean(s);
                             return null;
                         }
                         // 額外操作,幫助補足節點出棧。這個條件極少觸發
                         if ((h = head) != null && h.next == s)
                             casHead(h, s.next);
                         return (E) ((mode == REQUEST) ? m.item : s.item);
                     }
                 // 如果棧頂元素不是 fulfill,那麼從棧中匹配節點。如果匹配不到添加節點
                 } else if (!isFulfilling(h.mode)) {
                     // 如果棧頂節點已經取消,那麼彈出棧頂元素
                     if (h.isCancelled())
                         casHead(h, h.next);
                     // 如果棧頂元素有效,那麼構建新節點併入棧成爲棧頂節點。新節點是 FULFILLING 狀態
                     else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                         // 匹配補足節點
                         for (;;) {
                             // 如果有補足元素,那麼棧頂節點的後一個節點就是補足節點
                             // 因爲棧中的節點或者是所有的添加元素的節點(和還未來得及出棧的已經匹配到節點的節點),或者全部是獲取元素的節點。所以第一個另外一種模式的節點入棧,那麼後續的節點必然是非此模式的節點,否則就是模式一樣的節點。
                             SNode m = s.next;       // m is s's match
                             // m == null 之前是有能匹配到的節點的,現在丟失了說明有其他現在操作,改變棧數據。
                             // 如果可匹配的節點丟失,那麼退出本次循環重新走最外層循環邏輯
                             if (m == null) {
                                 // 將本次操作添加的棧頂元素彈出
                                 casHead(s, null);
                                 // 重新走外層循環時重新構建節點。
                                 s = null;
                                 break;
                             }
                             // 匹配到元素後,需要將棧頂和棧第二位置的節點一起彈出,需要將第三位置上的節點職位棧頂元素
                             SNode mn = m.next;
                             // 匹配校驗。如果校驗成功則並喚醒匹配到的節點的線程
                             if (m.tryMatch(s)) {
                                 // 將第三位置的元素設置爲棧頂元素
                                 casHead(s, mn);
                                 // 如果是 request 操作,返回匹配到的元素,如果是 date 操作,返回當前節點的元素
                                 return (E) ((mode == REQUEST) ? m.item : s.item);
                             // 匹配校驗失敗則將棧第二位節點彈出棧
                             } else
                                 s.casNext(m, mn);
                         }
                     }
                 // 如果棧頂已經是 FULFILLING,那麼幫助棧頂匹配補足節點。
                 // 額外操作,完成後繼續走循環代碼
                 } else {
                     SNode m = h.next;
                     if (m == null)
                         casHead(h, null);
                     else {
                         SNode mn = m.next;
                         if (m.tryMatch(h))
                             casHead(h, mn);
                         else
                             h.casNext(m, mn);
                     }
                 }
             }
         }
    
         /**
          * 等待匹配到補足元素
          *
          * @param s the waiting node
          * @param timed true if timed wait
          * @param nanos timeout value
          * @return matched node, or s if cancelled
          */
         SNode awaitFulfill(SNode s, boolean timed, long nanos) {
             // 等待超時時間
             final long deadline = timed ? System.nanoTime() + nanos : 0L;
             // 當前線程。如果線程需要被掛起,那麼會將線程指給節點上的 waiter,方便後續匹配到補足元素後喚醒該線程
             Thread w = Thread.currentThread();
             //自旋次數
             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;
                     }
                 }
                 // 判斷是否需要繼續自旋
                 if (spins > 0)
                     spins = shouldSpin(s) ? (spins-1) : 0;
                 // 自旋已經結束,此時還未找到補足節點,那麼將線程指給節點
                 else if (s.waiter == null)
                     s.waiter = w;
                 // 如果沒有設置超時時間,那麼掛起線程
                 else if (!timed)
                     LockSupport.park(this);
                 // 設置了超時時間,那麼計算掛起線程的等待時間並在這段時間內掛起線程
                 else if (nanos > spinForTimeoutThreshold)
                     LockSupport.parkNanos(this, nanos);
             }
         }
    
         /**
          * Returns true if node s is at head or there is an active
          * fulfiller.
          */
         boolean shouldSpin(SNode s) {
             SNode h = head;
             return (h == s || h == null || isFulfilling(h.mode));
         }
    
         /**
          * 將節點從棧中刪除
          */
         void clean(SNode s) {
             s.item = null;
             s.waiter = null;
    
             // 後繼節點
             SNode past = s.next;
             // 尋找有效的後繼節點
             if (past != null && past.isCancelled())
                 past = past.next;
    
             // 設置有效的棧頂元素
             SNode p;
             while ((p = head) != null && p != past && p.isCancelled())
                 casHead(p, p.next);
    
             // 檢查有效的棧頂元素和有效後繼節點是否存在取消節點,如果存在則將其剔出棧
             while (p != null && p != past) {
                 SNode n = p.next;
                 // 如果節點取消,那麼將節點剔出棧
                 if (n != null && n.isCancelled())
                     p.casNext(n, n.next);
                 // 遍歷後繼節點
                 else
                     p = n;
             }
         }
     }

隊列 transfer 操作邏輯

循環處理一下兩個操作:

  1. 如果棧是空棧或者和本次操作的模式一致(date、request、fulfill),構建節點入棧
  2. 如果棧正好能夠滿足操作(比如 date 請求,結果能夠匹配到一個 request),利用 CAS 方式將隊列中匹配到的節點更新成 fulfill 狀態,並將節點出隊。

awaitFulfill

transfer 方法根據是否有元素的操作判斷該操作是 date 還是 request,並依賴 awaitFulfill 處理等待匹配到 fulfill 操作。awaitFulfill 循環處理一下操作:

  1. 判斷是否中斷,如果中斷則取消對應節點
  2. 如果已經匹配到不足的節點,返回補足的節點
  3. 如果等待超時,那麼取消線程(取消線程會把節點的不足節點置爲自身)
  4. 在自旋次數未達到一定程度前,自旋等待補足節點
  5. 超過自旋次數,將當前線程的引用指給節點的 waiter,方便其他線程匹配喚醒當前線程(該線程將會被 LockSupport 掛起)
  6. LockSupport 掛起當前線程

代碼詳情:

    /**
     * dual queue 的實現類
     */
    static final class TransferQueue<E> extends Transferer<E> {
        /**
         * queue 是通過單向鏈表實現。QNode 是實現單向鏈表的節點
         */
        static final class QNode {
            // 下一個節點
            volatile QNode next;
            // 元素
            volatile Object item;
            // 添加節點的線程
            volatile Thread waiter;
            // 節點的模式。request、data、fullled
            final boolean isData;
    
            QNode(Object item, boolean isData) {
                this.item = item;
                this.isData = isData;
            }
    
            boolean casNext(QNode cmp, QNode val) {
                return next == cmp &&
                    UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
            }
    
            boolean casItem(Object cmp, Object val) {
                return item == cmp &&
                    UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
            }
    
            /**
             * 設置爲取消節點。將元素從 cmp 改成節點自身。
             */
            void tryCancel(Object cmp) {
                UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
            }
    
            /**
             * 判斷是否是取消節點。判斷的依據是如果元素時節點自身那麼就是取消節點。
             */
            boolean isCancelled() {
                return item == this;
            }
    
            /**
             * 判斷節點是否離開隊列。
             * 判斷的依據是下一個節點的指向指向自身
             */
            boolean isOffList() {
                return next == this;
            }
        }
    
        // 隊列頭節點
        transient volatile QNode head;
        // 隊列尾節點
        transient volatile QNode tail;
        /**
         * 取消但是還未從隊列中剔除的節點引用
         */
        transient volatile QNode cleanMe;
    
        /**
         * 構造方法,初始化一個虛擬節點或者頭結點。當隊列是空隊列的時候,隊尾指針也指向該節點。
         */
        TransferQueue() {
            QNode h = new QNode(null, false);
            head = h;
            tail = h;
        }
    
        /**
         * CAS 修改隊首節點,並將老節點的下一個節點的引用置空。
         */
        void advanceHead(QNode h, QNode nh) {
            if (h == head &&
                UNSAFE.compareAndSwapObject(this, headOffset, h, nh))
                h.next = h; // forget old next
        }
    
        /**
         * CAS 修改隊尾節點
         */
        void advanceTail(QNode t, QNode nt) {
            if (tail == t)
                UNSAFE.compareAndSwapObject(this, tailOffset, t, nt);
        }
    
        /**
         * CAS 修改取消但是沒有提出隊列的節點
         */
        boolean casCleanMe(QNode cmp, QNode val) {
            return cleanMe == cmp &&
                UNSAFE.compareAndSwapObject(this, cleanMeOffset, cmp, val);
        }
    
        /**
         * 添加和獲取元素的實現
         */
        @SuppressWarnings("unchecked")
        E transfer(E e, boolean timed, long nanos) {
    
            // 延遲初始化
            QNode s = null;
            // 本次添加節點的模式 queue 中只有
            boolean isData = (e != null);
    
            for (;;) {
                // 尾節點
                QNode t = tail;
                // 頭節點
                QNode h = head;
                // 隊列初始化時會構建一個虛擬節點,t == null || h == null 說明還未初始化完成,那麼自旋等待初始化完成
                // 在 SynchronousQueue 中,這種情況不會發生
                if (t == null || h == null)
                    continue;
                // 如果是空隊列或者是和尾結點擁有相同的模式,那麼做入隊操作,添加到隊尾。
                if (h == t || t.isData == isData) {
                    // 隊尾的下一個節點指向
                    QNode tn = t.next;
                    // t != tail 說明隊列發生變化,那麼重新走 for 循環,獲取最新的隊尾節點
                    if (t != tail)
                        continue;
                    // 如果隊尾的下一個節點指向的不是 null,那麼將下一個節點更新成最新的隊尾,然後重新走 for 循環,獲取最新的隊尾節點
                    if (tn != null) {
                        advanceTail(t, tn);
                        continue;
                    }
                    // 如果設置爲有超時時間,並且超時時間<=0,那麼直接返回 null 結束操作。
                    // 這段代碼一般是由於是調用 transfer 設置爲不需要等待導致(offer(E e)但是是空隊列)或者已經超過了等待時間。
                    if (timed && nanos <= 0)        // can't wait
                        return null;
                    // 如果節點未初始化,那麼初始化節點
                    if (s == null)
                        s = new QNode(e, isData);
                    // CAS 修改隊尾的下一個節點的屬性,改成。如果修改失敗表示隊尾節點已經發生變化,那麼從新走 for 循環,獲取最新的隊尾節點
                    if (!t.casNext(null, s))
                        continue;
                    // 修改隊列的隊尾節點,從 t 改成 s。
                    advanceTail(t, s);
                    // 等待匹配補足節點
                    Object x = awaitFulfill(s, e, timed, nanos);
                    // x == s 說明超時或者線程中斷,將節點提出隊列
                    if (x == s) {
                        clean(t, s);
                        return null;
                    }
                    // 此時已經配到到正確的結果,節點已經被取消。
                    // 如果此時節點還未出隊(s.next != s),那麼將 s 出隊
                    if (!s.isOffList()) {
                        // 如果尾結點是頭結點的話(空隊列),那麼將 s 置爲頭結點(頭結點是虛節點,相當於 s 出隊)
                        advanceHead(t, s);
                        // x != null 已經匹配過補足元素。如果沒有匹配到則 x == s 否則 s != s。到這一般是匹配到元素了。那麼將 s 取消(s.item = s)
                        if (x != null)
                            s.item = s;
                        s.waiter = null;
                    }
                    // 返回匹配到的節點的數據。如果是 date 操作,返回 null,如果是 request 返回補足點的 item 數據。
                    return (x != null) ? (E)x : e;
                // 隊列中存在補足節點,那麼尋找補足節點
                } else {
                    // 隊列中第一個有效的節點,也是計劃中的補足節點
                    QNode m = h.next;
                    // 如果隊列發生變化,那麼從新中循環獲取最新的隊列信息。
                    if (t != tail || m == null || h != head)
                        continue;
                    // 補足節點的數據
                    Object x = m.item;
                    /**
                     * 滿足以下任一條件則重新走循環代碼
                     * 1. m 節點已經是 fulfilled (被其他線程匹配)
                     * 2. m 節點被取消了(超時取消)
                     * 3. CAS 將 m 的 item 改成 e 失敗((被其他線程匹配或者取消超時)
                     */
                    if (isData == (x != null) ||    // m already fulfilled
                        x == m ||                   // m cancelled
                        !m.casItem(x, e)) {         // lost CAS
                        // 以上條件滿足說明 m 節點需要出隊。這裏利用 CAS 將隊首節點改成 m 節點,幫助其出隊。
                        advanceHead(h, m);          // dequeue and retry
                        continue;
                    }
                    // 到這說明 m 是補足節點,將 m 出隊
                    advanceHead(h, m);
                    // 幫助喚醒補足節點的線程
                    LockSupport.unpark(m.waiter);
                    // 返回匹配到的節點的數據。如果是 date 操作,返回 null,如果是 request 返回補足點的 item 數據。
                    return (x != null) ? (E)x : e;
                }
            }
        }
    
        /**
         * 等待匹配補足節點
         * 和 stack 的 awaitFulfill 的思路一致
         *
         * @param s 等待匹配補足節點的節點
         * @param e 節點的元素
         * @param timed 是否有超時設置。如果是那麼需要在超時時間<=0 時,不再繼續等待
         * @param nanos 超時時間
         * @return 如果匹配到補足節點,那麼返回補足節點的元素,否則返回自身
         */
        Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Thread w = Thread.currentThread();
            int spins = ((head.next == s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (;;) {
                if (w.isInterrupted())
                    s.tryCancel(e);
                Object x = s.item;
                if (x != e)
                    return x;
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        s.tryCancel(e);
                        continue;
                    }
                }
                if (spins > 0)
                    --spins;
                else if (s.waiter == null)
                    s.waiter = w;
                else if (!timed)
                    LockSupport.park(this);
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }
    
        /**
         * 將節點剔出隊列
         * @param pred 將被踢出隊列的節點的上一個節點
         * @param s 將被踢出隊列的節點
         */
        void clean(QNode pred, QNode s) {
            // 置空 waiter 屬性
            s.waiter = null;
            // 如果是 s 不是隊尾節點,那麼將 s 從隊列中刪除,否則將 pred 指給 cleanMe,並將上一個 cleanMe 指向的取消節點剔出隊列
            while (pred.next == s) {
                QNode h = head;
                // 第一個有效節點。隊列初始化時會初始化一個虛擬節點。
                QNode hn = h.next;
                // 如果第一個有效節點不爲空並且是取消的節點,那麼將其更新成爲頭結點,重新走循環
                if (hn != null && hn.isCancelled()) {
                    advanceHead(h, hn);
                    continue;
                }
                QNode t = tail;
                // 如果頭尾節點是同一個節點,說明隊列是空隊列結束循環
                if (t == h)
                    return;
                // 尾結點的下一個節點
                QNode tn = t.next;
                // 如果尾結點發生變化,那麼重新走循環獲取最新的尾結點
                if (t != tail)
                    continue;
                // 如果尾結點的下一個節點不是 null,那麼將其更新成最新的尾結點
                if (tn != null) {
                    advanceTail(t, tn);
                    continue;
                }
                // 如果取消的節點不是尾結點,那麼將 s 的上一個節點的下一個節點指向改成 s 的下一個節點(節點從單向鏈表中刪除)並結束循環
                if (s != t) {
                    QNode sn = s.next;
                    if (sn == s || pred.casNext(s, sn))
                        return;
                }
                // 獲取到上一次做 clean 操作時的 pred 節點。
                QNode dp = cleanMe;
                // dp != null 那麼需要將上一次做取消操作但是未從隊列中刪除的節點本次需要從隊列中刪除
                if (dp != null) {    // Try unlinking previous cancelled node
                    // 上一次被取消的節點
                    QNode d = dp.next;
                    QNode dn;
                    /*
                     * 判斷條件如果滿足,那麼將 cleanMe CAS 修改成 null
                     * 1. d == null 取消節點已經消失
                     * 2. d == dp 取消節點已經消失,並且 dp 也是取消的節點
                     * 3. d.isCancelled() == false,d 現在不是取消節點
                     * 4. d != t d 節點不是尾結點(尾結點不做從隊列剔出的操作)
                     * 5. (dn = d.next) != null d 節點有後續節點(d 節點非尾結點--尾結點不從隊列刪除)
                     * 6. dp.casNext(d, dn) == true CAS 將 d 節點剔出隊列操作成功
                     */
                    if (d == null ||               // d is gone or
                        d == dp ||                 // d is off list or
                        !d.isCancelled() ||        // d not cancelled or
                        (d != t &&                 // d not tail and
                         (dn = d.next) != null &&  //   has successor
                         dn != d &&                //   that is on list
                         dp.casNext(d, dn)))       // d unspliced
                        casCleanMe(dp, null);
                    // dp == pred 說明 cleanMe 已經存儲過了直接退出循環
                    if (dp == pred)
                        return;
                // cleanMe 不存在,將 cleanMe 從 null 改成 pred。修改成功則退出否則重新走循環
                } else if (casCleanMe(null, pred))
                    return;
            }
        }
    }

出隊方法

SynchronousQueue 提供了三個出隊方法:

  1. public E take() throws InterruptedException:該方法會一直阻塞線程直到其他線程添加元素或者線程中斷。
  2. public E poll(long timeout, TimeUnit unit) throws InterruptedException:該方法支持設置超時時間。如果超過超時時間還未獲取到數據或者等待過程中賢臣剛被中斷,那麼線程不會繼續掛起。如果超時時間設置爲0,隊列如果有補足節點那麼返回補足節點的元素,否則直接結束,返回null。
  3. public E poll():通2中超時時間設置爲0的情況。

代碼詳情:

    /**
     * 等待獲取元素。
     * 該方法會一直阻塞知道獲取到元素或者等待線程被中斷。
     *
     * @return the head of this queue
     * @throws InterruptedException {@inheritDoc}
     */
    public E take() throws InterruptedException {
        // 基於阻塞並且無超設置的transfer方法獲取元素。
        E e = transferer.transfer(null, false, 0);
        // e != null 說明獲取到元素,那麼直接返回,否則就是等待線程被中斷了。
        if (e != null)
            return e;
        // LockSupport的park方法當遇到線程中斷時會將線程喚醒,並將線程置爲未取消中斷的狀態。所以這幾繼續將線程置爲中斷並拋出中斷異常。
        Thread.interrupted();
        throw new InterruptedException();
    }
    
    /**
     * 獲取元素
     * 該方法會在設置的超時時間等一直等待獲取元素。如果超時還未獲取到元素那麼返回null。
     *
     * @return the head of this queue, or {@code null} if the
     *         specified waiting time elapses before an element is present
     * @throws InterruptedException {@inheritDoc}
     */
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        // 基於可超時的transfer方法等待獲取元素
        E e = transferer.transfer(null, true, unit.toNanos(timeout));
        // 如果線程爲中斷獲取獲取到元素那麼返回元素,否則拋出中斷遺產。
        if (e != null || !Thread.interrupted())
            return e;
        throw new InterruptedException();
    }
    
    /**
     * 獲取元素
     * 該方法只有在同步隊列中有其他線程添加元素並被阻塞時纔會返回元素,否則返回null。
     * 該方法不會阻塞線程。
     *
     * @return the head of this queue, or {@code null} if no
     *         element is available
     */
    public E poll() {
        return transferer.transfer(null, true, 0);
    }

入隊方法

SynchronousQueue 提供了三個入隊方法:

  1. public void put(E e) throws InterruptedException:線程會一直阻塞直到有其他線程獲取對應的元素或者被中斷。
  2. public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException:線程在設置的超時時間內會一直阻塞直到超時獲取有其他線程獲取到對應的元素或者被中斷。
  3. public boolean offer(E e):該方法不阻塞線程。如果已經有其他線程等待獲取元素,那麼該方法添加元素成功,否則直接失敗。

代碼如下:

    /**
     * 添加元素
     * 線程會一直阻塞直到有其他線程獲取到該元素或者線程被中斷
     * 該方法不支持添加null元素。如果元素等於null,拋出NullPointerException異常
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        //檢查元素是否合法
        if (e == null) throw new NullPointerException();
        // 基於阻塞並且無超時時間限制的transfer添加元素。如果返回null說明線程被中斷。
        if (transferer.transfer(e, false, 0) == null) {
            // LockSupport.park方法阻塞的線程如果被中斷,那麼線程會被喚醒並且職位未中斷。此時將線程置爲中斷,並拋出中斷異常
            Thread.interrupted();
            throw new InterruptedException();
        }
    }
    
    /**
     * 添加元素
     * 線程會在超時時間內一直等待添加元素直到獲取到元素或者超時或者線程被中斷。
     * 不支持null元素
     *
     * @return {@code true} if successful, or {@code false} if the
     *         specified waiting time elapses before a consumer appears
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        // 校驗元素有效性
        if (e == null) throw new NullPointerException();
        // 基於超時時間的transfer方法添加元素。如果獲取的元素不等於null,說明添加成功,返回true。
        if (transferer.transfer(e, true, unit.toNanos(timeout)) != null)
            return true;
        // 如果線程未被中斷說明是超時,那麼返回false
        if (!Thread.interrupted())
            return false;
        // 說明線程被中斷。
        throw new InterruptedException();
    }
    
    /**
     * 添加元素
     * 只有同步隊列中存在等待獲取元素的線程,該方法添加元素才能成功,否則直接添加失敗返回false
     *
     * @param e the element to add
     * @return {@code true} if the element was added to this queue, else
     *         {@code false}
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        return transferer.transfer(e, true, 0) != null;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章