Queue常用類解析之PriorityQueue
Queue常用類解析之ConcurrentLinkedQueue
Queue常用類解析之BlockingQueue(一):PriorityBlockingQueue、DelayQueue和DelayedWorkQueue
Queue常用類解析之BlockingQueue(二):ArrayBlockingQueue
Queue常用類解析之BlockingQueue(三):LinkedBlockingQueue
Queue常用類解析之BlockingQueue(四):SynchronousQueue
接着上文對BlockingQueue的介紹繼續向下
八、LinkedTransferQueue
1. 接口TransferQueue
TransferQueue是jdk1.7新增的一個接口,是阻塞隊列LinkedBlockingQueue的子接口。
TransferQueue適用於生產者必須等待消費者進行接收數據的阻塞隊列場景。
在普通的BlockingQueue中,入隊操作和出隊操作彼此分離,彼此都只和隊列打交道,只需要往隊列中存取數據,不需要關心對方的存在。而在TransferQueue中是一種生產者-消費者的應用場景,生產者和消費者之間並不是毫無關係的,生成者生成數據需要等待着一個消費者前來接受,否則就是一次失敗的入隊操作。因此在TransferQueue的入隊和出隊操作以外,還有一個更重要的操作概念稱爲transfer,指生產者將數據傳遞給消費者的過程。
transfer方法表示生產者傳遞數據給消費者進行消費,tryTransfer則表示在非阻塞或者限時阻塞的情況下這一過程是否成功。
TransferQueue也可以具有一個固定的容量,事實上SynchronousQueue就是一個典型的容量爲0的TransferQueue,雖然其並沒有實現TransferQueue接口,在容量爲0的TransferQueue中,只有消費者接收了數據才能put成功,因此put操作和transfer操作是同義的。
TransferQueue定義了5個方法。
getWaitingConsumerCount和hasWaitingConsumer都與隊列中的等待的消費者有關,前者返回等待的消費者數目,後者返回是否有等待的消費者。
tranfer和兩個tryTransfer方法都用於transfer操作,分別是阻塞方法,非阻塞方法和限時阻塞方法。
2. 靜態內部類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; // null until waiting
和SychronousQueue$TransferQueue#QNode類基本一致,有4個屬性。
同樣的也有3個特殊狀態:
item值爲自身:取消狀態,表示指定時間內沒有找到匹配節點或者線程被中斷。被匹配成功時邏輯處理完成後也會將item置爲自身。
isData爲true但是item爲null:不是request節點,也不是data節點,表示一種邏輯節點,一般用於head節點
上述狀態的節點稱爲matched節點,上述狀態以外的節點爲unmatched節點,是真正需要被匹配的節點。
next值爲自身:表示節點從鏈表中移除,稱爲offList,在遍歷過程中,該類型的節點的下一個節點被視爲head節點而不是節點的next節點。
對於item屬性,request節點時item爲null,data節點時item爲數據的具體值,被成功匹配時item爲匹配節點的item值,被成功匹配時的匹配邏輯執行完成後爲節點自身,取消時爲節點自身。
2. LinkedTransferQueue簡述
LinkedTransferQueue是一個基於鏈表結果的無界的TransferQueue。
和大多數的集合不同,由於LinkedTransferQueue中存在着異步操作,因此size()方法並不是一個O(1)的方法,而是需要對集合進行遍歷,而且由於遍歷過程中隊列可能被修改,其返回的結果也並不一定是實時一致的。另外,批量方法的操作也無法保證原子性。
隊列中並不直接存儲元素,而是封裝成data節點(put)或request節點(take),當一個data節點入隊時,如果遇到request節點,進行匹配並將節點出隊,反之亦然。
由於LinkedTransferQueue是無界隊列,所以所以的入隊方法(add,put,offer)都是成功的。
/** head of the queue; null until first enqueue */
//頭結點,head.next是隊列中的第一個有效節點
transient volatile Node head;
/** tail of the queue; null until first append */
//尾節點,隊列中的最後一個有效節點
private transient volatile Node tail;
/** The number of apparent failures to unsplice removed nodes */
//unsplice方法出的失敗次數
private transient volatile int sweepVotes;
head表示第一個節點,head可能是matched節點,那麼此時head.next是第一個unmatched節點,tail最後一個有效節點(unmatch節點)。一般希望head.next,如下圖第一張。
但是LinkedTransferQueue允許更新的滯後性,存在更新的滯後性,如下圖第二張。
3. LinkedTransferQueue#xfer(Object, boolean, int, long)
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
boolean isData = p.isData;
Object item = p.item;
if (item != p && (item != null) == isData) { // unmatched //選取unmatch節點嘗試匹配
if (isData == haveData) // can't match //節點模式相同(都是request節點或者都是data節點),不能匹配,離開內循環
break;
if (p.casItem(item, e)) { // match 匹配成功
for (Node q = p; q != h;) {
Node n = q.next; // update by 2 unless singleton
//h依舊是head節點,說明沒有其它線程干擾,那麼從head節點開始到q都是matched節點,可以從鏈表中移除
//以q.next(不存在則選取q)作爲新的head節點
if (head == h && casHead(h, n == null ? q : n)) {
//head節點修改成功,原來的head節點修改成offlist類型,因爲遍歷線程可以將head節點作爲offlist節點的遍歷過程的下一個節點
h.forgetNext();
break;
} // advance and retry //其它線程對head節點進行了修改
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
//head節點不存在 || head.next不存在 說明鏈表中沒有有意義的節點(request節點和data節點都不存在),不需要修改head節點
// head.next是unmatched節點,head節點已經不需要修改
break; // unless slack < 2
}
//喚醒線程
LockSupport.unpark(p.waiter);
//返回被匹配節點的item屬性
return LinkedTransferQueue.<E>cast(item);
}
}
Node n = p.next;
p = (p != n) ? n : (h = head); // Use head if p offlist //從下個節點繼續匹配,offlist節點則以頭結點作爲下一個節點
}
//沒有unmatched節點或者所有節點模式相同(都是request節點或者都是data節點)
//NOW模式不做任何處理
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)
//SYNC和TIMED模式還需要通過自旋/阻塞進行等待
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
//NOW和ASYNC模式以及TIMED模式超時後的返回
return e; // not waiting
}
}
整體流程爲找到鏈表中的第一個unmatched節點,如果該節點模式是不同的,嘗試進行匹配,匹配成功後更新頭結點。如果匹配,則重試。
如果鏈表中不存在unmatched節點或者節點模式相同無法匹配,則根據參數how模式的不同進行相應的處理。
/*
* Possible values for "how" argument in xfer method.
*/
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
xfer方法時LinkedTransferQueue的核心方法,transfer,tryTransfer以及入隊、出隊方法的邏輯都由該方法完成。
xfer方法的how參數,共4種模式:
非阻塞方法(無參的poll方法和tryTransfer方法)爲NOW模式,匹配不成功認爲失敗,不做任何處理
對於入隊的offer, put和add爲ASYNC模式,異步處理,匹配不成功加入隊列,不需要自旋/阻塞方式以等待匹配,直接返回
阻塞方法(transfer和take方法)爲SYNC模式,同步處理,匹配不成功加入隊列,匹配成功前一直自旋/阻塞
限時阻塞方法(帶時間參數的poll和tryTransfer方法)爲TIMED,匹配不成功加入隊列,匹配成功前自旋/阻塞一段時間
4. LinkedTransferQueue#tryAppend(Node, boolean)
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
//p是null節點,將head賦給p。p爲null時有兩種場景:
//1. tail爲null,此時head也爲null或者只有head一個節點
//2. 在後續邏輯中,p爲offList時置爲null,即遍歷到offList節點時從head節點重新遍歷
if (p == null && (p = head) == null) { //head節點不存在,新節點作爲head
if (casHead(null, s))
return s; // initialize
}
else if (p.cannotPrecede(haveData)) //tail節點爲unmatched節點且與待插入節點的模式不同,可以進行匹配
return null; // lost race vs opposite mode
else if ((n = p.next) != null) // not last; keep traversing // p不是最後一個節點,重新確定p值
//p != t(p從tail節點開始已經遍歷完成了一個節點)且tail節點發生了變化,從tail節點開始重新遍歷
//否則(p剛從tail節點開始遍歷或者tail節點沒有發生變化),繼續向下遍歷。如果p是offList節點,則置爲null從頭開始(下一步的循環中會從head取值)
//看不明白爲什麼要加 p != t的判斷,爲什麼不是tail發生變化即取 p = tail ?
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
//s追加到p節點後面,更新tail
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方法將參數中的節點加入鏈表尾部,返回參數節點在鏈表中的前一個節點(如果參數節點作爲head節點插入鏈表,則返回參數節點自身)。
整體流程爲:
如果隊列中沒有節點,參數節點作爲head節點。
如果隊列中有節點,從tail開始遍歷到最後一個節點,如果存在可以與參數節點匹配的unmatched節點,不做處理並返回null(xfer方法調用tryAppend方法發現返回null會重試匹配)。否則將參數節點作爲最後一個節點的下一個節點,並更新tail。
在最後的更新邏輯中,可以看到是一個if語句下的循環,並不是每次都會更新tail,將該語句簡化以後如下:
if (p != t) {
while (true) {
if(tail == t && casTail(t, s)) {
break;
}
t = tail;
if(t == null) {
break;
}
s = t.next;
if(s == null) {
break;
}
s = s.next;
if(s == null) {
break;
}
if(s == t) {
break;
}
}
}
可以看到在 p == t時是不更新tail的
剩下的場景中
(1)如果tail沒有變化,更新tail節點,此時至少插入了兩個新節點
(2)如果tail發生了變化,只有在tail節點存在且tail節點後面至少有兩個非offlist的節點時更新tail節點。
綜上所述,tail節點後至少有兩個新節點纔會更新tail,而不是每次插入新節點都會更新。
5.LinekdTransferQueue#awaitMatch(Node, Node, Object, boolean, long)
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 //s.item = s, s.waiter = null。被成功匹配後最終item屬性也是自身
return LinkedTransferQueue.<E>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 //waiter賦值,準備下一步的阻塞,waiter屬性用於被匹配後喚醒線程
}
else if (timed) { //限時阻塞
nanos = deadline - System.nanoTime();
if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
}
else { //永久阻塞
LockSupport.park(this);
}
}
}
用於線程自旋或阻塞,直到節點被成功匹配或超時或線程中斷。
節點被成功匹配,返回匹配的節點的item值。
線程中斷或超時,節點取消,移除節點,返回節點自身的item值。
6. LinkedTransferQueue#unsplice(Node, Node)
final void unsplice(Node pred, Node s) {
s.forgetContents(); // forget unneeded fields
/*
* See above for rationale. Briefly: if pred still points to
* s, try to unlink s. If s cannot be unlinked, because it is
* trailing node or pred might be unlinked, and neither pred
* nor s are head or offlist, add to sweepVotes, and if enough
* votes have accumulated, sweep.
*/
if (pred != null && pred != s && pred.next == s) { // pred節點存在且不是offlist節點且pred.next == s成立
Node n = s.next;
if (n == null || // s是最後一個節點
(n != s && pred.casNext(s, n) && pred.isMatched())) { // 如果s不是最後一個節點且s不是offlist節點,則通過pred.next = s.next移除s節點。如果pred不是matched節點,return結束。否則還需要後續處理
//嘗試更新head節點,從原來的head節點到prev(或s)之間找到第一個unmatched節點作爲新的head節點(如果沒有找到,則以最後一個matched節點作爲head節點)
for (;;) { // check if at, or could be, head
Node h = head;
if (h == pred || h == s || h == null) //pred是head節點 || s是head節點 || head節點不存在(隊列中沒有節點),return結束
return; // at head or list empty
if (!h.isMatched()) //head節點未匹配,離開循環
break;
Node hn = h.next;
if (hn == null) //head節點是matched節點且head.next不存在,空隊列
return; // now empty
if (hn != h && casHead(h, hn)) //更新head節點,head節點
h.forgetNext(); // advance head
}
//sweepVotes計數及處理
if (pred.next != pred && s.next != s) { // recheck if offlist
for (;;) { // sweep now if enough votes
int v = sweepVotes;
if (v < SWEEP_THRESHOLD) {
if (casSweepVotes(v, v + 1)) //增加節點移除的失敗次數casSweepVotes
break;
}
else if (casSweepVotes(v, 0)) {//節點移除的失敗次數casSweepVotes清0,觸發sweep方法進行全局清除
sweep();
break;
}
}
}
}
}
}
unsplice方法用於將節點從鏈表中移除。
(1)prev節點或者s節點是offlist節點,不會觸發head節點更新和sweepVotes計算和處理的邏輯。
(2) 當s節點不是最後一個節點且不是offList節點,通過CAS方式移除s節點,失敗則結束方法,不會觸發head節點更新和sweepVotes計算和處理的邏輯。
(3)步驟2中通過通過CAS方式移除s節點成功,但prev節點是unmatcher節點,結束方法,不會觸發head節點更新和sweepVotes計算和處理的邏輯。
除了上述以外的場景都會進入head節點更新和sweepVotes計算和處理的邏輯。
在head節點更新的邏輯中
(1)如果pred或者s是頭結點,renturn結束,對應原來隊列中除了s節點以外沒有節點(或者都是matched節點)的場景
(2)如果遍歷到了最後一個節點,return結束,對應prev和s節點已經不再鏈表中的場景,否則遍歷到prev或s節點就會結束遍歷
除了這兩種情況外,節點更新邏輯結束之後就會進入sweepVotes計數及處理。
7. LinkedTransferQueue#sweep()
private void sweep() {
for (Node p = head, s, n; p != null && (s = p.next) != null; ) {
if (!s.isMatched()) //unmatched節點,跳過
// Unmatched nodes are never self-linked
p = s;
else if ((n = s.next) == null) // trailing node is pinned
break; //最後一個節點,離開循環
else if (s == n) // stale
// No need to also check for p == s, since that implies s == n
p = head; //offlist節點,從head節點重新開始遍歷
else
p.casNext(s, n); //matched節點,移除
}
}
sweep節點進行全局掃描清除,移除matched節點