SynchronousQueue
是一個同步阻塞隊列,它的每個插入操作都要等待其他線程相應的移除操作,反之亦然。SynchronousQueue 像是生產者和消費者的會合通道,它比較適合“切換”或“傳遞”這種場景:一個線程必須同步等待另外一個線程把相關數據傳遞給它。
不同於之前的 ArrayBlockingQueue,LinkedBlockingQueue…對於它們來說生產者線程將數據放入存儲空間(數組,隊列),消費者線程從空間中拿數據,雙方彼此之間不交互,而 SynchronousQueue
則完全不同,其內部沒有存儲空間,無論是生產者線程還是消費者線程,都將其創建爲一個節點,節點構成鏈,生產者節點去鏈中匹配消費者,反之亦然。雙方通過這種方式來通信。
關於節點鏈: SynchronousQueue
提供了兩種策略:公平與非公平(默認非公平模式)。公平策略下,能夠保證等待最久的消費者線程能夠優先拿到元素或者等待最久的生產者線程能夠優先轉交元素。非公平通過棧(LIFO)實現,公平通過隊列(FIFO)實現,對應的類爲TransferStack
與 TransferQueue
,各自節點類型 SNode
與 QNode
。隊列通常用於支持更高的吞吐量,棧則支持更高的線程局部存儲。
TransferStack
與 TransferQueue
都繼承自 Transferer
,該接口只有一個方法abstract E transfer(E e, boolean timed, long nanos);
,無論是 取 或是 添加 操作都調用該方法,因爲在 SynchronousQueue 中這兩個動作都是一個意思,就是取匹配。
關於節點:
類的繼承圖:
源碼解析
在之前分析的等待隊列實現都會用到鎖,而 SynchronousQueue 沒有用到鎖,來看看他的實現吧,會讓你受益匪淺的。
構造器
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
默認非公平模式,創建 TransferStack
來操作鏈。
添加 與 取操作,如 poll, take, put…都調用了 transferer.transfer(…),意思是 匹配:生產者 匹配 消費者,消費者 匹配 生產者 ,添加 與 取 的語義都轉化爲了 匹配。
TransferStack
代碼的目的是要實現功能,在介紹源碼之前先來概述 TransferStack 實現的功能。之前說 取/加 操作都變爲 匹配,生產者線程匹配消費者線程,消費者線程匹配生產者線程,我們以 SNode 節點作爲每個請求操作的實體,節點之間構成 鏈,匹配就變成了節點去鏈中找尋節點,實現並沒有用到鎖,而是建立在 CAS 之上,也就是建立在失敗重試的基礎上,它確保了每一步的安全性,但是沒有鎖的排斥併發下多線程對鏈表同時操作,那又如何確保節點匹配的安全性 ?節點去鏈表中找尋另一個節點的過程叫匹配,只要保證併發下當有節點在執行匹配時,其它線程就不能改變鏈表,如果可以使用鎖的話便可以鎖住代碼以排斥其它線程,不能使用鎖我們就需要設計鏈表的操作規則以實現這種功能,如何實現我們從代碼中找尋答案。
匹配的核心是 transfer 方法,在介紹它之前先將其用到的方法,變量進行講解。
static final class TransferStack<E> extends Transferer<E> {
/** 等待匹配的消費者 */
static final int REQUEST = 0;
/** 等待匹配的生產者 */
static final int DATA = 1;
/** 正在匹配的節點 */
static final int FULFILLING = 2;
既然生產者消費者的節點爲統一類型,如這裏的 SNode
,那麼節點就需要有身份標識,REQUEST 代表消費者,DATA 代表生產者,正在執行匹配邏輯的節點標識爲 FULFILLING,一來是防止其它人匹配它,二來當其它線程檢測到該狀態的節點會去幫助其執行匹配操作。
static final class SNode {
volatile SNode next; // 堆中的下一個節點
volatile SNode match; // 指向匹配的節點
volatile Thread waiter; // 指定阻塞/喚醒的線程,在阻塞前會將該字段指向當前運行線程
// 爲什麼下面兩個不用 volatile 修飾 ?
Object item; // 存儲數據,對於消費者則爲null
int mode; // 標識該節點身份/狀態
// 二者的賦值發生在節點對象創建階段,之後不會再對其進行更改。
// Note: item and mode fields don't need to be volatile
// since they are always written before, and read after,
// other volatile/atomic operations.
SNode(Object item) {
this.item = item;
}
// cas 將當前節點的next指向由 cmp 改爲 val
boolean casNext(SNode cmp, SNode val) {
return cmp == next &&
UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
/**
* 這段代碼的調用是這樣的,假設當前正在匹配的節點 s ,
* 它首先會嘗試匹配其 next 指向的節點,假設爲 m ,
* 他會這麼調用 m.tryMatch(s),將 m 節點的 match 指向自己,
* 即匹配,若成功則代表二者相匹配,從鏈表中刪除二者。
* 這裏有四點:1,m 可能在阻塞,所以需要判斷是否需要喚醒,
* 判斷依據便是其 waiter 字段是否爲空。
* 2,頭節點的匹配操作並非只有頭節點一個線程在執行,
* 線程們創建自己的節點競爭壓入棧頂,但當頭節點正在執行匹配邏輯時,
* 想要壓入棧頂的線程都會失敗,轉而去幫助頭節點進行匹配操作。
* 這就是這裏曹永cas更改match的原因。
* 匹配成功的線程是將 m 節點的 match 由 null 改爲 xx 的線程
* 3,match不爲null,可能是其它線程幫助完成了匹配操作,或者當前節點
* 處於刪除狀態,刪除狀態的節點其match指向自己。
* 4,最後 return match == s; 而不是直接返回false,併發下
* 執行匹配的線程中有一個成功怎麼讓其它所有的線程都知道,也就是返回true
*
* Tries to match node s to this node, if so, waking up thread.
* Fulfillers call tryMatch to identify their waiters.
* Waiters block until they have been matched.
*
* @param s the node to match
* @return true if successfully matched to s
*/
boolean tryMatch(SNode s) {
if (match == null &&
UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
// 若是被匹配節點 m 是阻塞狀態,則其 waiter 指向 m 的線程
// 這裏獲取它並喚醒。別喚醒後線程執行邏輯會回到 awaitFulfill 方法
// 之後回到 transfer 方法,下面會介紹它們。
Thread w = waiter;
if (w != null) { // waiters need at most one unpark
waiter = null;
LockSupport.unpark(w);
}
return true;
}
return match == s;
}
/**
* 在超時,中斷兩種情況下我們會調用該方法,標識該節點爲刪除狀態,
* 通過將 match 指向自身來標識,刪除狀態的節點將不被匹配,。
* 畢竟併發下情況複雜,並不能保證結果,
* 這也就要求你在實現中考慮到操作達不到預期結果時該如何處理,
* 這些你都可以在 transfer 方法中看到。
* Tries to cancel a wait by matching node to itself.
*/
void tryCancel() {
UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}
// 判斷該節點是否已處於刪除狀態
boolean isCancelled() {
return match == this;
}
// Unsafe mechanics
......省略 Usafe 相關
}
接下來繼續回到 TransferStack 中,來介紹它的方法。
// 代表棧頂,插入取出都在頭部
volatile SNode head;
isFulfilling:判斷節點是否正在執行匹配操作。
/**
* 首先先來介紹下節點創建時 mode 的設定:
* 當前線程要執行的操作與頭節點相同,即 mode 相同,不去爲其執行匹配操作,
* 創建節點,mode標識其身份 REQUEST 或 DATA,等待被其它節點匹配。
* 若是不同,我們將節點的 mode 置爲 FULFILLING | mode,該節點會執行匹配邏輯
* 所以判斷一個節點是否在執行匹配操作,只需讓其 mode & FULFILLING,
* 不等於0則代表其正在執行匹配邏輯。
*/
static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }
casHead:更改頭節點
// 將頭節點由 h 改爲 nh
boolean casHead(SNode h, SNode nh) {
return h == head &&
UNSAFE.compareAndSwapObject(this, headOffset, h, nh);
}
snode:創建或更改節點。
/**
* 該方法很簡單就是創建/更改節點,不過要理解其設計得先看其用處
* 該方法只在 transfer 方法中被調用,節點創建後利用 CAS 將節點壓入堆頂
* 但併發下存在競爭,若是入棧失敗,線程再次嘗試仍會調用 snode 方法
* 此時節點已經創建不能重複創建,堆也可能發生變化,所以節點的 next 需要更新
* 之前說了節點是否執行匹配邏輯是根據此時堆頂節點情況,不同情況mode也會變化
* 所以 mode 也要更新
* Creates or resets fields of a node. Called only from transfer
* where the node to push on stack is lazily created and
* reused when possible to help reduce intervals between reads
* and CASes of head and to avoid surges of garbage when CASes
* to push nodes fail due to contention.
*/
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;
}
awaitFulfill:在阻塞前輪循多次。
鏈表中的節點分三種:正在執行匹配操作的節點,等待被匹配的節點,刪除狀態的節點。等待被匹配也就是阻塞,但在真正阻塞前會嘗試多次循環,以這種方式來等待被匹配,畢竟線程的阻塞喚醒是有消耗的,而且併發下可能也只需等待很短的時間便會被匹配,這是種優化手段。
先來看看其他一些要用到的方法,變量:
shouldSpin:判斷是否需要繼續循環等待。awaitFulfill一開始會利用該方法來計算循環次數,在循環開始後每一次循環都會通過該方法來校驗循環是否該繼續。
// 節點 s 是頭節點,或是頭節點正在執行匹配操作時返回true
boolean shouldSpin(SNode s) {
SNode h = head;
return (h == s || h == null || isFulfilling(h.mode));
}
節點 s 在兩種情況下需要繼續循環:1,s 是堆頂節點,堆頂節點邏輯到這說明其next節點是與其相同操作的節點,s 不執行匹配邏輯,這裏採取的措施就是讓其循環等待下一個入棧節點,可能會匹配它。2,堆頂節點此時在執行匹配邏輯,可能匹配的就是 s ,所以讓 s 多循環以等待被匹配。
// 帶時間的阻塞的循環次數,受核數影響
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;
// 由於非時間阻塞比時間阻塞少幾步時間操作,所以更快,
// 所以其循環次數多於時間阻塞
static final int maxUntimedSpins = maxTimedSpins * 16;
// 對於快要到期的阻塞也就沒必要取阻塞了,用循環代替,到期仍沒有被匹配
// 會調用 tryCancel 將節點標識爲刪除狀態
static final long spinForTimeoutThreshold = 1000L;
下面開始介紹awaitFulfill方法:
SNode awaitFulfill(SNode s, boolean timed, long nanos) {
// 終止時間
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread(); // 獲取當前執行線程,阻塞的就是該線程
// 計算循環次數
int spins = (shouldSpin(s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 線程中斷則將節點置爲刪除狀態,刪除狀態的節點不會被匹配
// 之後clean方法會刪除該節點。
//
// 之所以在這裏校驗線程狀態是因爲,阻塞用的是park,
// 線程從阻塞狀態恢復除了被其它線程unpark外,還有可能是因爲
// 線程被中斷,所以這裏需要堆中斷進行處理。
if (w.isInterrupted())
s.tryCancel();
// 若 match 不爲 null,代表該節點被匹配或是刪除,返回 match指向的節點
SNode m = s.match;
if (m != null)
return m;
// 時間阻塞
if (timed) {
// 計算剩餘時間
nanos = deadline - System.nanoTime();
// 超時,則置節點爲刪除狀態,在輪詢一次以退出
if (nanos <= 0L) {
s.tryCancel();
continue;
}
}
// 循環次數不爲0,shouldSpin 判斷是否該繼續輪循,不該則
// 將輪循次數置爲0
if (spins > 0)
spins = shouldSpin(s) ? (spins-1) : 0;
// 阻塞前先將節點的 waiter 字段指向當前運行線程。
// 主動匹配的節點線程便是通過 waiter 字段來獲取該被匹配節點的線程,
// 從而unpark喚醒。
else if (s.waiter == null)
s.waiter = w; // establish waiter so can park next iter
else if (!timed)
LockSupport.park(this);
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
當該節點仍未被匹配(包括中斷,超時導致的刪除) 會阻塞,當其從阻塞狀態恢復(可能是被匹配所以被喚醒,可能是中斷,或超時),再次循環對中斷,匹配,超時進行相應處理,總之最後返回該節點 match 字段指向的節點,之後再向上回到transfer 方法,下面先來介紹該方法。
transfer
transfer:匹配的實現。
無論取還是添加,都是去節點鏈裏匹配與自己相反操作的線程,配對成功二者交換數據,生產者將產品交給了消費者。
線程會構造節點,節點之間形成鏈,那麼 transfer 究竟構造了一個怎樣的鏈 ?
對於該節點鏈來說只有兩種情況:1,頭節點未在執行匹配操作,則該鏈上所有節點的 mode 相同,即都是同一種操作的線程節點。2,頭節點正在執行匹配邏輯,則鏈上除頭節點外其它所有節點的 mode 相同,且都與頭節點相反,匹配未完成之前頭節點不會發生變動。
如何構造這樣一個鏈?
首先聲明下 mode 的概念,mode 有三種值:REQUEST,DATA,FULFILLING|mode,分別表示取,添加,正在匹配。
1,鏈表爲空或當前線程要執行的操作與頭節點相同,構造節點壓入棧頂,阻塞等待。
2,線程與頭節點mode不同,且頭節點未在執行匹配操作,構造節點將其mode置爲FULFILLING|mode,壓入棧頂,執行匹配邏輯。
3,線程與頭節點mode不同,且頭節點正在執行匹配操作,線程去幫助頭節點匹配。
爲什麼構造這樣一個鏈?
我們想要實現的是 取/加 操作線程相互匹配,而匹配過程並沒有用鎖保護,那麼又建立在什麼基礎之上?答案是 CAS,建立在失敗重試的基礎上,CAS 加上這樣一種鏈二者保證了併發下 匹配 功能的實現。
下面來看看具體的實現過程:
E transfer(E e, boolean timed, long nanos) {
//節點被創建後會賦給s,snode 方法通過也是通過其來判斷是創建或修改節點
SNode s = null; // constructed/reused as needed
// 通過是否帶數據來判斷身份,生產者或消費者
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
SNode h = head; // 首先獲取頭節點
// 鏈表爲空,或與頭節點的操作相同
if (h == null || h.mode == mode) { // empty or same-mode
// 這裏的處理與想的很不一樣,首先聲明一點,
// 線程運行到這之前不知道循環了多少次,不過有一點可以確定
// 假設線程創建了節點,但它也一定沒有添加進鏈表,可以從整個代碼中看出這一點
// 也因此在這裏沒必要有刪除節點操作。
// 但是仍然很奇怪,爲什麼不直接返回null,多了一步對頭節點刪除狀態的操作
// 那麼超時線程可能再次循環,之後它可能去協助頭節點匹配,
// 可能自己加入鏈表執行匹配,也就是說這裏的設計並不要求超時只能返回null
//
// 當前線程超時
if (timed && nanos <= 0) { // can't wait
// 若頭節點處於刪除狀態則CAS更新頭節點,也達到了刪除的目的
if (h != null && h.isCancelled())
casHead(h, h.next); // pop cancelled node
else
return null; // 返回null
// 構造節點加入棧頂
} else if (casHead(h, s = snode(s, e, h, mode))) {
// 該方法是種優化手段,在阻塞前以循環的方式來等待被匹配
// 返回值爲 s 的 match 字段,其指向匹配節點或自身
// 指向自身代表刪除狀態
SNode m = awaitFulfill(s, timed, nanos);
// 節點爲刪除狀態,clean刪除節點
if (m == s) { // wait was cancelled
clean(s);
return null;
}
// 正在執行匹配操作的節點一定是頭節點,在匹配過程中鏈表不會被其它線程更改
// 既然如此篤定那又何必在此多個if判斷,直接CAS彈出head和s不就行了
// 頭節點匹配成功後會對鏈表頭進行更新,所以被匹配節點從awaitFulfill
// 返回後運行到這需要對當前堆頂進行判斷
if ((h = head) != null && h.next == s)
casHead(h, s.next); // help s's fulfiller
// 若當前線程是消費者則從其匹配節點那拿到數據返回,否則返回null
return (E) ((mode == REQUEST) ? m.item : s.item);
}
// 頭節點正在執行匹配操作,其它線程都會跳到第三步,就是最後的else代碼塊
// 去幫助頭節點執行匹配操作。
} else if (!isFulfilling(h.mode)) { // try to fulfill
// 頭節點刪除狀態則CAS更新頭節點
if (h.isCancelled()) // already cancelled
casHead(h, h.next); // pop and retry
// 執行匹配前,創建當前線程的節點,並CAS壓入棧頂,成功後執行匹配邏輯
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
// 下面就是上面一直提到的匹配邏輯代碼
// 頭節點從next開始在鏈表查找匹配的節點
for (;;) { // loop until matched or waiters disappear
// m 爲 頭節點也就是 s 將要進行匹配的節點
SNode m = s.next; // m is s's match
// m爲null說明鏈表中只剩下 s 了,找不到匹配節點該怎麼辦?
// 置頭節點爲null,s 本代表本線程創建的節點,也將其置爲null
// 目的是讓線程重新回到最外層的循環,以重新開始
if (m == null) { // all waiters are gone
casHead(s, null); // pop fulfill node
s = null; // use new node next time
break; // restart main loop
}
// 頭節點s嘗試匹配其next指向的節點m
// 成功則將mn置爲新的頭節點,然後根據身份來返回數據
// 匹配失敗則將s的next更新爲mn,這樣也就刪除了m
// 爲什麼刪除m,此時的鏈表情況是什麼樣的:頭節點的mode與其它節點相反
// 其它節點mode相同,tryMatch返回false說明該節點爲刪除狀態,
// 所以這裏可以刪除它
SNode mn = m.next;
if (m.tryMatch(s)) {
casHead(s, mn); // pop both s and m
return (E) ((mode == REQUEST) ? m.item : s.item);
} else // lost match
s.casNext(m, mn); // help unlink
}
}
// 當此時頭節點正在匹配,其它任何嘗試將自己的節點壓入棧頂的線程
// 都將失敗,之後會到此來幫助頭接點匹配
} 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
}
}
}
}
clean
該方法在 TransferStack 中只在一處被調用,在 transfer 中,當線程從 awaitFulfill 中返回,發現節點處於刪除狀態,則調用 clean 刪除節點,導致的原因可能是中斷或超時。
單向鏈表的刪除需要從開頭一直開始遍歷,直到找到該節點。
clean 方法中,以刪除節點的 next 或 next.next 爲邊界,然後從頭開始遍歷到該邊界,刪除所有遇到的刪除狀態的節點。
void clean(SNode s) {
s.item = null; // forget item
s.waiter = null; // forget thread
// past就是s的next節點,當該節點也處於刪除狀態就設爲下一個
// 那若下一個仍爲刪除狀態呢?不用管,這裏主要是找一個邊界,
// 確保s被刪除
SNode past = s.next;
if (past != null && past.isCancelled())
past = past.next;
// 找到真正的頭節點
// Absorb cancelled nodes at head
SNode p;
while ((p = head) != null && p != past && p.isCancelled())
casHead(p, p.next);
// 一直遍歷到past,刪除遇到的所有刪除狀態的節點
// Unsplice embedded nodes
while (p != null && p != past) {
SNode n = p.next;
if (n != null && n.isCancelled())
p.casNext(n, n.next);
else
p = n;
}
}
TransferQueue
static final class QNode {
// 節點無效後next會指向自身,防止GC無法回收這些無效節點
volatile QNode next; // next node in queue
// item 有三種狀態:一開始代表數據,刪除狀態時指向自身,
// 被匹配時其等於匹配線程的數據。
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;
}
boolean casNext(QNode cmp, QNode val) {
return next == cmp &&
UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
// 匹配指的是匹配線程將節點的item改爲自己的數據item的值
boolean casItem(Object cmp, Object val) {
return item == cmp &&
UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
/**
* 將item指向自身,代表刪除狀態
*/
void tryCancel(Object cmp) {
UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
}
boolean isCancelled() {
return item == this;
}
/**
* 節點出列後其next會指向自身,實現子advanceHead 中
* Returns true if this node is known to be off the queue
* because its next pointer has been forgotten due to
* an advanceHead operation.
*/
boolean isOffList() {
return next == this;
}
// Unsafe mechanics
// Unsafe相關
}
一些方法及變量
/** 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 was cancelled.
*/
transient volatile QNode cleanMe;
TransferQueue() {
QNode h = new QNode(null, false); // initialize to dummy node.
head = h;
tail = h;
}
/**
* 注意在更新頭節點後,會將舊頭節點的next指向自身,以便被GC回收
* Tries to cas nh as new head; if successful, unlink
* old head's next node to avoid garbage retention.
*/
void advanceHead(QNode h, QNode nh) {
if (h == head &&
UNSAFE.compareAndSwapObject(this, headOffset, h, nh))
h.next = h; // forget old next
}
/**
* Tries to cas nt as new tail.
*/
void advanceTail(QNode t, QNode nt) {
if (tail == t)
UNSAFE.compareAndSwapObject(this, tailOffset, t, nt);
}
/**
* Tries to CAS cleanMe slot.
*/
boolean casCleanMe(QNode cmp, QNode val) {
return cleanMe == cmp &&
UNSAFE.compareAndSwapObject(this, cleanMeOffset, cmp, val);
}
transfer
該方法實現的是這樣一種鏈表:鏈表上所有節點的模式相同,即要麼全是取要麼全是加,若當前線程模式與鏈表節點相同則加入隊列尾,若相反則從頭部取一線程與其匹配。接下來從源碼來看實現的細節。
E transfer(E e, boolean timed, long nanos) {
QNode s = null; // 當前操作線程的節點
boolean isData = (e != null); // 是否是生產者
for (;;) {
QNode t = tail;
QNode h = head;
// 看見未初始化的head與tail,循環等待初始化完成
if (t == null || h == null) // saw uninitialized value
continue; // spin
// 鏈表爲空或當前線程操作與鏈表節點相同,構造節點加入鏈表尾
if (h == t || t.isData == isData) { // empty or same-mode
QNode tn = t.next;
// tail 更新需重新循環
if (t != tail) // inconsistent read
continue;
// t的next非空說明其並非尾節點,也就是tail指針過時
// 更新tail指向tn,重新循環
if (tn != null) { // lagging tail
advanceTail(t, tn);
continue;
}
// 超時返回null
if (timed && nanos <= 0) // can't wait
return null;
// 線程未創建節點則創建
if (s == null)
s = new QNode(e, isData);
// 將線程的節點加入隊列尾部,失敗則重新循環
if (!t.casNext(null, s)) // failed to link in
continue;
// 線程節點添加成功後,更新tail指針
advanceTail(t, s); // swing tail and wait
// 線程先以循環方式等待,仍未被匹配後進入阻塞,方法返回可能因爲三種情況
// 中斷,超時,被匹配,返回的是節點的item字段的值,前兩種情況x爲節點自身
// 被匹配返回的是匹配線程的數據
Object x = awaitFulfill(s, e, timed, nanos);
// 中斷或超時則刪除該節點並返回null
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
// 匹配線程們競爭匹配的是head.next節點,head節點是個虛節點,
// 假設頭節點h,其next爲s,匹配線程們競爭匹配s,無論是成功的那個
// 或是其它失敗的,它們都會調用advanceHead,將頭節點更新爲s,
// advanceHead 更新頭節點時會將舊head的next指向自身,
// 成功匹配的線程還會喚醒阻塞的 s 線程,
// s 從awaitFulfill返回後運行到此,
// 首先調用isOffList,該方法通過next指針是否指向自身來判斷節點是否已出列,
// 而在這裏該方法返回false的話說明s是此時的頭節點,我們要將其
// next,item,waiter 字段置空以利於垃圾回收哪些資源。
// 當頭節點繼續往下更新,也就是advanceHead的調用,s的next會指向自身
// 這時候 s 也就真正出列了。
// isOffList返回true的話,說明s節點線程執行時頭節點早已更新到其後
// 也就是 s 已出列。
if (!s.isOffList()) { // not already unlinked
// 若s一開始讀取的頭節點t仍未過時,則幫助匹配線程更新頭節點
advanceHead(t, s); // unlink if head
// 節點線程能夠執行到者說明其被匹配,那麼其item已被改爲匹配線程的數據,
// 所以s.item要麼爲null,我們不需做處理;要麼不爲null,
// 說明匹配線程是生產者,我們需要清除節點對生產者數據的引用,
// 將其改爲指向自身,表明該數據處於刪除狀態應該被刪除。
// x 與 s.item 都指向匹配線程的數據對象。
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
// 這裏不對線程做區分統一返回生產者的數據
return (x != null) ? (E)x : e;
// head是虛節點,匹配的是其next指向的節點
} else { // complementary-mode
QNode m = h.next; // node to fulfill
// t,h過時或無節點可匹配,繼續循環
if (t != tail || m == null || h != head)
continue; // inconsistent read
// item 有三種狀態:一開始代表數據,刪除狀態時指向自身,
// 被匹配時其等於匹配線程的數據。
// 判斷一個節點是否可以匹配,1,節點沒被匹配。2,節點不是刪除狀態
// 未被匹配的節點都符合哪些條件,分析後才能寫出判斷語句。
// 假設1,當前匹配線程是 消費者,則 isData爲false,
// 鏈表上節點都爲生產者,所以 x 不爲null。
// 2,當前匹配線程是 生產者,則isData爲true,
// 鏈表上節點爲消費者,所以 x 等於 null。
// 總結可得到 isData == (x != null),這種分析忽略了節點刪除狀態的問題
// 所以其返回false並不能代表節點可匹配,仍需對節點是否刪除進行判斷
Object x = m.item;
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
// 運行到這說明節點可匹配,cas競爭更改節點的item,
// 匹配成功的線程就是將節點item改爲自己的數據e的線程
!m.casItem(x, e)) { // lost CAS
// 對於不可匹配的,刪除狀態的,競爭更改失敗的都會去更新頭部,
// 爲了下一個節點的匹配
advanceHead(h, m); // dequeue and retry
continue;
}
// 匹配成功的線程也會更新頭部
advanceHead(h, m); // successfully fulfilled
// 喚醒節點阻塞的線程
LockSupport.unpark(m.waiter);
// 這裏不對線程做區分統一返回生產者的數據
return (x != null) ? (E)x : e;
}
}
}
awaitFulfill:與 TransferStack 實現基本相同
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
/* Same idea as TransferStack.awaitFulfill */
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);
// 鏈表中節點的item值被更改有兩個原因:1,由於超時,中斷,
// tryCancel的調用使得item指向自身。2,匹配成功,
// 匹配線程將該被匹配節點的item改爲自己的數據對象
// 這兩種情況下,節點線程回到transfer方法
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);
}
}
clean
TransferQueue 與 TransferStack 都有clean 方法,二者實現不同皆是因爲鏈表的處理不同,對於 TransferStack 來說我們不知道刪除節點的前置節點,所以需要從頭開始往後遍歷;而 TransferQueue 我們是直到其前置節點的,唯一需要注意的是刪除節點是尾節點的情況,不能刪除尾節點,因爲這樣會破壞鏈表,之後的節點無法再加入,所以該種情況我們使用 cleanMe 來存儲其前置節點,留待下次clean 的調用,clean 方法每此都會檢查 cleanMe ,若其next不再是尾節點則刪除其next。
void clean(QNode pred, QNode s) {
s.waiter = null; // forget thread
/*
* At any given time, exactly one node on list cannot be
* deleted -- the last inserted node. To accommodate this,
* if we cannot delete s, we save its predecessor as
* "cleanMe", deleting the previously saved version
* first. At least one of node s or the node previously
* saved can always be deleted, so this always terminates.
* 不刪除尾節點因爲這樣會破壞鏈表,若s是尾節點則將pred標記爲cleanMe,留待下次刪除
* clean方法會檢查cleanMe字段的值,然後刪除其next節點。
*/
while (pred.next == s) { // Return early if already unlinked
QNode h = head;
QNode hn = h.next; // Absorb cancelled first node as head
// 找到真正的頭節點
if (hn != null && hn.isCancelled()) {
advanceHead(h, hn);
continue;
}
QNode t = tail; // Ensure consistent read for tail
// 鏈表爲空
if (t == h)
return;
// 確保t爲尾節點
QNode tn = t.next;
if (t != tail)
continue;
if (tn != null) {
advanceTail(t, tn);
continue;
}
// s並非尾節點,嘗試刪除它
if (s != t) { // If not tail, try to unsplice
QNode sn = s.next;
if (sn == s || pred.casNext(s, sn))
return;
}
// 檢查cleanMe字段,不爲空則刪除其next節點
QNode dp = cleanMe;
if (dp != null) { // Try unlinking previous cancelled node
QNode d = dp.next;
QNode dn;
// d刪除後將cleanMe置空
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);
// pred等於dp,說明s仍爲尾節點,退出
if (dp == pred)
return; // s is already saved node
// s 爲尾節點,將pred標記爲cleanMe
} else if (casCleanMe(null, pred))
return; // Postpone cleaning s
}
}