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