一、SynchronousQueue API介紹
public class SynchronousQueue<E>extends AbstractQueue<E>implements BlockingQueue<E>, Serializable
一種阻塞隊列,其中每個 put 必須等待一個 take,反之亦然。同步隊列沒有任何內部容量,甚至連一個隊列的容量都沒有。不能在同步隊列上進行 peek,因爲僅在試圖要取得元素時,該元素才存在;除非另一個線程試圖移除某個元素,否則也不能(使用任何方法)添加元素;也不能迭代隊列,因爲其中沒有元素可用於迭代。隊列的頭 是嘗試添加到隊列中的首個已排隊線程元素;如果沒有已排隊線程,則不添加元素並且頭爲 null。對於其他 Collection 方法(例如 contains),SynchronousQueue
作爲一個空集合。此隊列不允許 null 元素。
同步隊列類似於 CSP 和 Ada 中使用的 rendezvous 信道。它非常適合於傳遞性設計,在這種設計中,在一個線程中運行的對象要將某些信息、事件或任務傳遞給在另一個線程中運行的對象,它就必須與該對象同步。
對於正在等待的生產者和使用者線程而言,此類支持可選的公平排序策略。默認情況下不保證這種排序。但是,使用公平設置爲 true 所構造的隊列可保證線程以 FIFO 的順序進行訪問。公平通常會降低吞吐量,但是可以減小可變性並避免得不到服務。
此類及其迭代器實現 Collection 和 Iterator 接口的所有可選 方法。
此類是 Java Collections Framework 的成員。
同步隊列類似於 CSP 和 Ada 中使用的 rendezvous 信道。它非常適合於傳遞性設計,在這種設計中,在一個線程中運行的對象要將某些信息、事件或任務傳遞給在另一個線程中運行的對象,它就必須與該對象同步。
對於正在等待的生產者和使用者線程而言,此類支持可選的公平排序策略。默認情況下不保證這種排序。但是,使用公平設置爲 true 所構造的隊列可保證線程以 FIFO 的順序進行訪問。公平通常會降低吞吐量,但是可以減小可變性並避免得不到服務。
此類及其迭代器實現 Collection 和 Iterator 接口的所有可選 方法。
此類是 Java Collections Framework 的成員。
二、核心方法
1、put
public void put(E o) throws InterruptedException {
if (o == null) throw new NullPointerException();
if (transferer.transfer(o, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
2、offer
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
return transferer.transfer(e, true, 0) != null;
}
3、take
public E take() throws InterruptedException {
Object e = transferer.transfer(null, false, 0);
if (e != null)
return (E)e;
Thread.interrupted();
throw new InterruptedException();
}
4、poll
public E poll() {
return (E)transferer.transfer(null, true, 0);
}
三、內部實現類TransferStack
1、數據結構SNode
該結構是一個鏈表結構,並且裏面的屬性next,match以及waiter都是volatile類型的
該結構提供了tryMatch的方法,將match指向給定的SNode節點,並且如果給定的節點有等待的線程,則置爲null,並且解除等待線程的阻塞狀態
該結構提供的tryCancel方法,則是使用cas算法將match指向自身。
/** Node class for TransferStacks. */
static final class SNode {
volatile SNode next; // next node in stack
volatile SNode match; // the node matched to this
volatile Thread waiter; // to control park/unpark
Object item; // data; or null for REQUESTs
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;
}
boolean casNext(SNode cmp, SNode val) {
return cmp == next &&
UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
/**
* 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
*/
//如果match對象爲空,則使用cas算法將match指向給定的節點s,
//如果節點s有等待的線程,則置爲空,並且解除等待線程的阻塞方法
boolean tryMatch(SNode s) {
if (match == null && //如果match爲空,則將當前節點的match 置爲節點s
UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
Thread w = waiter; //如果當前節點的等待線程不爲空,則先置爲空,並且解除等待線程的阻塞狀態
if (w != null) { // waiters need at most one unpark
waiter = null;
LockSupport.unpark(w);
}
return true;
}
return match == s;
}
/**
* 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
private static final sun.misc.Unsafe UNSAFE;
private static final long matchOffset;
private static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class k = SNode.class;
matchOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("match"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
2、核心方法 transfer
Object transfer(Object e, boolean timed, long nanos) {
/*
* Basic algorithm is to loop trying one of three actions:
*
* 1. If apparently empty or already containing nodes of same
* mode, try to push node on stack and wait for a match,
* returning it, or null if cancelled.
*
* 2. If apparently containing node of complementary mode,
* try to push a fulfilling node on to stack, match
* with corresponding waiting node, pop both from
* stack, and return matched item. The matching or
* unlinking might not actually be necessary because of
* other threads performing action 3:
*
* 3. If top of stack already holds another fulfilling node,
* help it out by doing its match and/or pop
* operations, and then continue. The code for helping
* is essentially the same as for fulfilling, except
* that it doesn't return the item.
*/
SNode s = null; // constructed/reused as needed
int mode = (e == null) ? REQUEST : DATA;//如果e爲空則說明是出棧(take,poll)操作,否則是入棧(put,offer)操作,e代表入棧的數據
for (;;) {
SNode h = head;
// 如果棧頂爲空或者棧頂的模式和 傳遞過來的請求模式一樣,
//ps:一開始的話 棧頂肯定爲空;請求模式一樣,可以理解爲當前操作與棧頂是同類型的操作,都是入棧或者出棧
if (h == null || h.mode == mode) { // empty or same-mode
if (timed && nanos <= 0) { // can't wait //如果有超時限制,這塊暫時不研究
if (h != null && h.isCancelled())
casHead(h, h.next); // pop cancelled node
else
return null;
} else if (casHead(h, s = snode(s, e, h, mode))) { //首先創建新的節點s並且壓棧,
//等待線程中斷或者阻塞或者節點s的match元素不爲空
//如果線程中斷,則節點s的match指向自身,
//循環完畢N次之後,設置s節點的等待線程爲當前線程,如果沒有超時限制,則調用方法
//LockSupport.park(this); 來阻塞當前線程,
//直到 其它操作獲取到節點s之後,操作s的match元素
//之後獲取到s節點的等待線程,通過 unpark 解除線程的阻塞狀態
SNode m = awaitFulfill(s, timed, nanos); //
if (m == s) { // wait was cancelled//如果線程被中斷,則返回null
clean(s);
return null;
}
//如果棧頂不爲空,並且棧頂指向的next是s,則將棧頂置換爲s指向的下個節點
if ((h = head) != null && h.next == s)
casHead(h, s.next); // help s's fulfiller
//如果當前mode是出棧操作,則返回m的item,如果當前mode是入棧操作,則返回s的item
//注意此時的棧的數據結構是 s是新的節點,m 是s的match元素,
//如果是入棧,則說明前一操作是出棧,但是沒有數據,所以一直在等待,這時候入棧,正好滿足需求
//如過是出棧,則說明前一操作是入棧,所以可以直接獲取到入棧的數據
return (mode == REQUEST) ? m.item : s.item;
}
} else if (!isFulfilling(h.mode)) { // try to fulfill 此時可以確定head!=null&&h.mode!=mode
//如果發生了取消,則h的match指向自身,置換棧頂(h)的元素爲h的next節點
if (h.isCancelled()) // already cancelled
casHead(h, h.next); // pop and retry
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {//創建新的節點s,並且壓棧
for (;;) { // loop until matched or waiters disappear
SNode m = s.next; // m is s's match //獲取s的next節點,相當於舊的head
if (m == null) { // all waiters are gone //重複檢查如果m 爲空,
casHead(s, null); // pop fulfill node //如果此時的棧頂元素爲s,那麼將棧頂元素置爲空
s = null; // use new node next time
break; // restart main loop
}
SNode mn = m.next; //獲取m的下一個節點 mn
if (m.tryMatch(s)) { //如果節點m的match設置爲s成功,
casHead(s, mn); // pop both s and m //如果棧頂元素爲s,將棧頂元素置爲mn,
return (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
}
}
}
}
3、核心方法 awaitFulfill
long lastTime = timed ? System.nanoTime() : 0; //是否設置了超時設置
Thread w = Thread.currentThread();
SNode h = head;
int spins = (shouldSpin(s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
if (w.isInterrupted()) //如果當前線程被中斷,則調用節點的tryCancel方法,實則是原子操作,如果s的match爲null,則將s的match 指向自身
s.tryCancel();
SNode m = s.match;
if (m != null) //如果match 不爲空,則返回m
return m;
if (timed) {
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
if (nanos <= 0) {
s.tryCancel();
continue;
}
}
if (spins > 0) //spins大於0 的時候一直遞減,直到爲0
spins = shouldSpin(s) ? (spins-1) : 0;
else if (s.waiter == null) //此時循環了 spins 次,仍然沒有退出循環,即沒有返回數據,如果節點s的等待線程爲空,則設置爲當前線程
s.waiter = w; // establish waiter so can park next iter
else if (!timed) //如果沒有設置超時限制,
LockSupport.park(this); //設置當前線程爲阻塞狀態
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
個人分析得出一個大致的結論使用SynchronousQueue,在非公平策略下,隊列的入隊和出隊操作會被封裝成Snode節點並且壓棧,
當前後兩個操作不一致,其中一個是入隊另一個是出隊,這種情況發生的時候,
TransferStack會一次性彈出 棧頂的2個元素,並且設置第一個元素的match 指向第二個元素,並且解除第二個元素等待線程的阻塞狀態。然後將棧頂元素置爲第二個元素的next節點。
如果第一個元素是入隊,那麼第二個元素是出隊,返回入隊的數據;如果第一個元素是出隊操作,第二個元素是入隊,返回入隊的數據,
判斷操作是入隊還是出隊,可以根據傳入transfer方法的 Object e 是否爲空來判斷,如果是入隊,則e不爲空,否則e爲空。
判斷連續的兩個操作是否一致,可以將棧頂的mode 與當前操作的mode 比較是否一致來判斷。
return (mode == REQUEST) ? m.item : s.item; 可以將m理解爲第二個元素,s理解爲第一個元素。