JDK並發工具類源碼學習系列——SynchronousQueue__java

來源:互聯網
上載者:User

http://blog.csdn.net/vickyway/article/details/50113429

https://zhuanlan.zhihu.com/p/29227508

SynchronousQueue是一種特殊的阻塞隊列,不同於LinkedBlockingQueue、ArrayBlockingQueue和PriorityBlockingQueue,其內部沒有任何容量,任何的入隊操作都需要等待其他線程的出隊操作,反之亦然。如果將SynchronousQueue用於生產者/消費者模式,那麼相當於生產者和消費者手遞手交易,即生產者生產出一個貨物,則必須等到消費者過來取貨,方可完成交易。 
SynchronousQueue有一個fair選項,如果fair為true,稱為fair模式,否則就是unfair模式。fair模式使用一個先進先出的隊列儲存生產者或者消費者線程,unfair模式則使用一個後進先出的棧儲存。 基本原理

SynchronousQueue通過將入隊出隊的線程綁定到隊列的節點上,並藉助LockSupport的park()和unpark()實現等待,先到達的線程A需調用LockSupport的park()方法將當前線程進入阻塞狀態,知道另一個與之匹配的線程B調用LockSupport.unpark(Thread)來喚醒在該節點上等待的線程A。 
基本邏輯: 初始狀態隊列為null 當一個線程到達,如果隊列為null,無與之匹配的線程,則進入隊列等待;隊列不為null,參考3 當另一個線程到達,如果隊列不為null,則判斷隊列中的第一個元素(針對fair和unfair不同)是否與其匹配,如果匹配則完成交易,不匹配則也入隊;隊列為null,參考2 常用方法解析

在深入分析其實現機制之前,我們先瞭解對於SynchronousQueue可執行哪些操作,由於SynchronousQueue的容量為0,所以一些針對集合的操作,如:isEmpty()/size()/clear()/remove(Object)/contains(Object)等操作都是無意義的,同樣peek()也總是返回null。所以針對SynchronousQueue只有兩類操作: 入隊(put(E)/offer(E, long, TimeUnit)/offer(E)) 出隊(take()/poll(long, TimeUnit)/poll())

這兩類操作內部都是調用Transferer的transfer(Object, boolean, long)方法,通過第一個參數是否為null,來區分是生產者還是消費者(生產者不為null)。 
針對以上情況,我們將著重分析Transferer的transfer(Object, boolean, long)方法,這裡由於兩種不同的公平模式,會存在兩個Transferer的衍生類別:

public SynchronousQueue(boolean fair) {    transferer = (fair)? new TransferQueue() : new TransferStack();}
1 2 3

可見fair模式使用TransferQueue,unfair模式使用TransferStack,下面我們將分別對這兩種模式進行著重分析。 fair模式

fair模式使用一個FIFO的隊列儲存線程,TransferQueue的結構如下:

/** Dual Queue */static final class TransferQueue extends Transferer {    /** Node class for TransferQueue. */    static final class QNode {        volatile QNode next;          // next node in queue        volatile Object item;         // CAS'ed to or from null        volatile Thread waiter;       // to control park/unpark        final boolean isData;        QNode(Object item, boolean isData) {            this.item = item;            this.isData = isData;        }        ...    }    /** Head of queue */    transient volatile QNode head;    /** Tail of queue */    transient volatile QNode tail;    /**     * Reference to a cancelled node that might not yet have been     * unlinked from queue because it was the last inserted node     * when it cancelled.     */    transient volatile QNode cleanMe;    TransferQueue() {        QNode h = new QNode(null, false); // initialize to dummy node.        head = h;        tail = h;    }    ...}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

以上是TransferQueue的大致結構,可以看到TransferQueue同一個普通的隊列,同時存在一個指向隊列頭部的指標——head,和一個指向隊列尾部的指標——tail;cleanMe的存在主要是解決不可清楚隊列的尾節點的問題,後面會介紹到;隊列的節點通過內部類QNode封裝,QNode包含四個變數: next:指向隊列中的下一個節點 item:節點包含的資料 waiter:等待在該節點上的線程 isData:表示該節點由生產者建立還是由消費者建立,由於生產者是放入資料,所以isData==true,而消費者==false

其他的內容就是一些CAS變數以及操作,下面主要分析TransferQueue的三個重要方法:transfer(Object, boolean, long)、awaitFulfill(QNode, Object, boolean, long)、clean(QNode, QNode)。這三個方法是TransferQueue的核心,入口是transfer(),下面具體看代碼。 transfer

/** * @By Vicky:交換資料,生產者和消費者通過e==null來區分 */Object transfer(Object e, boolean timed, long nanos) {    QNode s = null; // constructed/reused as needed    boolean isData = (e != null);// e==null,則isData==false,else idData==true    for (;;) {// 迴圈        QNode t = tail;        QNode h = head;        if (t == null || h == null)         // 無視即可,具體資訊在方法開始的注釋中有提到            continue;                       // spin        // h==t隊列為null,tail的isData==isData表示該隊列中的等待的線程與當前線程是相同模式        //(同為生產者,或者同為消費者)(隊列中只存在一種模式的線程)        // 此時需要將該線程插入到隊列中進行等待        if (h == t || t.isData == isData) {             QNode tn = t.next;            if (t != tail)                  // inconsistent read                continue;            // 這裡的目的是為了協助其他線程完成入隊操作            if (tn != null) {               // lagging tail                // 原子性將tail從t更新為tn,即將tail往後移動,直到隊列的最後一個元素                advanceTail(t, tn);                continue;            }            // 如果nanos<=0則說明不等待,那麼到這裡已經說到隊列沒有可匹配的線程,所以直接返回null即可            if (timed && nanos <= 0)        // can't wait                return null;            // 僅初始化一次s,節點s會儲存isData資訊作為生產者和消費者的區分            if (s == null)                s = new QNode(e, isData);            // 原子性的更新t的next指標指向s,上面將tail從t更新為tn就是為了處理此處剩下的操作            // 由於此處插入一個節點分成了兩個步驟,所以過程中會插入其他線程,導致看到不一致狀態            // 所以其他線程會執行剩下的步驟協助其完成入隊操作            if (!t.casNext(null, s))        // failed to link in                continue;            // 如果自己執行失敗沒有關係,會有其他線程幫忙執行完成的,所以才無需鎖,類似ConcurrentLinkedQueue            advanceTail(t, s);              // swing tail and wait            // 等待匹配            Object x = awaitFulfill(s, e, timed, nanos);            // 這裡有兩種情況:            //  A:匹配完成,返回資料            //  B:等待逾時/取消,返回原節點s            if (x == s) {                   // wait was cancelled                // 情況B則需要清除掉節點s                clean(t, s);                return null;            }            // 情況A,則匹配成功了,但是還需要將該節點從隊列中移除            //  由於FIFO原則,所以匹配上的元素必然是隊列的第一個元素,所以只需要移動head即可            if (!s.isOffList()) {           // not already unlinked                // 移動head指向s,則下次匹配從s.next開始                advanceHead(t, s);          // unlink if head                // 清除對節點中儲存的資料的引用,GC友好                if (x != null)              // and forget fields                    s.item = s;                s.waiter = null;            }            return (x != null)? x : e;        } else {                            // complementary-mode            // 進行匹配,從隊列的頭部開始,即head.next,非head            QNode m = h.next;               // node to fulfill            if (t != tail || m == null || h != head)                continue;                   // inconsistent read            // 判斷該節點的isData是否與當前線程的isData匹配            // 相等則說明m已經匹配過了,因為正常情況是不相等才對            // x==m說明m被取消了,見QNode的tryCancel()方法            // CAS設定m.item為e,這裡的e,如果是生產者則是資料,消費者則是null,            // 所以m如果是生產者,則item變為null,消費者則變為生產者的資料            // CAS操作失敗,則直接將m出隊,CAS失敗說明m已經被其他線程匹配了,所以將其出隊,然後retry            Object x = m.item;            if (isData == (x != null) ||    // m already fulfilled                x == m ||                   // m cancelled                !m.casItem(x, e)) {         // lost CAS                advanceHead(h, m);          // dequeue and retry                continue;            }            // 與m匹配成功,將m出隊,並喚醒等待在m上的線程m.waiter            advanceHead(h, m);              // successfully fulfilled            LockSupport.unpark(m.waiter);            return (x != null)? x : e;        }    }}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.