JUC源碼分析-容器-SynchronousQueue

概述

沒有容量的阻塞隊列,它的每個插入操作都要等待其他線程相應的移除操作,反之亦然。

SynchronousQueue(後面稱SQ)內部沒有容量,所以不能通過peek方法獲取頭部元素;也不能單獨插入元素,可以簡單理解爲它的插入和移除是“一對”對稱的操作

SQ 爲等待過程中的生產者或消費者線程提供可選的公平策略(默認非公平模式)。非公平模式通過棧(LIFO)實現,公平模式通過隊列(FIFO)實現。使用的數據結構是雙重隊列(Dual queue)和雙重棧(Dual stack)(後面詳細講解)。

算法分析

以Dual stack爲例

源碼中實現了一種隊列結構的算法

隊列中有兩個角色 傳遞者和接受者, 傳遞者之間排隊,接受者之間排隊,默認第一個入隊的節點是接受者,後面入隊的節點的模式與這個節點相同的爲接受者,不同的爲傳遞者。按照入隊順序,傳遞者和接受者配對後2個節點同時出隊。

 

圖解如下

A是接受者,B是傳遞者

 

源碼中的 取數據和放數據 都可以作爲接受者和傳遞者,實現了 取數據和放數據各自的排隊,並且只有取和放數據同時配對才能執行的功能。

數據的傳遞怎麼實現??

傳遞者與接受者配對時,將數據傳遞給接受者。接受者拿到的數據後根據自己是取數據或放數據,返回不同的結果。

取數據的方法:take(),poll(),poll(timeout) 放數據的方法:put(E e),offer(E e), offer(E e,timeout)

取數據的方法和放數據的方法都依賴transfer()實現,以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();

}

}

transferer是個什麼鬼?

transferer在初始化時候創建

public SynchronousQueue(boolean fair) {

transferer = fair ? new TransferQueue() : new TransferStack();

}

默認使用TransferStack實現。

 

下先分析一下TransferStack.transfer()

 

理解這個隊列的算法後,再來看核心部分的源碼就比較容易理解了

源碼分析

 

put/take方法核心實現

算法的基本循環包括以下三個行爲

  1. 如果隊列中爲空,或與頭部包含的節點但是模式相同,作爲接受者入隊,等待傳遞者匹配成功後,返回傳遞過來的數據。
  2. 與頭部包含的節點模式不同,作爲傳遞者入隊。尋找接受者匹配後,將數據傳遞給接受者,出隊這兩個節點。
  3. 如果隊列中的頭結點被其他傳遞者佔據,幫助傳遞者完成匹配和出隊工作,然後重試入隊。

 

 

Object transfer(Object e, boolean timed, long nanos) {

SNode s = null; // constructed/reused as needed

int mode = (e == null) ? REQUEST : DATA;

 

for (;;) {

SNode h = head;

if (h == null || h.mode == mode) { //行爲(1)

if (timed && nanos <= 0) { // 如果標識超時,且等待時間<=0,不能等待

if (h != null && h.isCancelled())

casHead(h, h.next); // 出隊被取消的節點

else

return null;

} else if (casHead(h, s = snode(s, e, h, mode))) {// 接受者入隊

SNode m = awaitFulfill(s, timed, nanos);// 等待傳遞者 匹配(後面詳細分析)

if (m == s) { // wait was cancelled // 等待被取消時成立。 awaitFulfill()中的邏輯

clean(s);

return null;

}

if ((h = head) != null && h.next == s) // 傳遞者執行完匹配還沒有出隊

casHead(h, s.next); // 幫助傳遞者出隊

return (mode == REQUEST) ? m.item : s.item;//根據是否是請求數據返回不同的對象。

}

} else if (!isFulfilling(h.mode)) { //判斷是否有傳遞者正在執行中

if (h.isCancelled()) // already cancel

casHead(h, h.next); // pop and retry

else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) { // 作爲傳遞者入隊,模式改爲傳遞者執行中

for (;;) { // loop until matched or waiters disappear

SNode m = s.next; // m is s's match

if (m == null) { // all waiters are gone 要匹配的節點已經離開隊列

casHead(s, null); // pop fulfill node 將頭節點 置爲null

s = null; // use new node next time 丟棄剛創建的節點

break; // restart main loop // 重新開始主循環

}

SNode mn = m.next;

//嘗試配對 接受者,並傳遞數據

if (m.tryMatch(s)) {//後面詳細分析

casHead(s, mn); // 出隊匹配完成的兩個節點。

return (mode == REQUEST) ? m.item : s.item;

} else // lost match

s.casNext(m, mn); // help unlink 持有m節點的線程執行等待被打斷後執行了取消,導致m不能被匹配。此時m要被出隊,當前線程從頭 開始匹配。

}

}

} else { // 幫助完成傳遞者的工作,在併發時會出現。

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

}

}

}

 

}

 

//只有節點爲null時,賦值給match參數

boolean tryMatch(SNode s) {

if (match == null &&

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;

}

 

awaitFulfill在等待匹配中先進行自旋,自旋如果被取消,自行匹配,返回。等待到限制自旋次數結束後,調用 LockSupport.park進入等待。

 

SNode awaitFulfill(SNode s, boolean timed, long nanos) {

 

long lastTime = timed ? System.nanoTime() : 0;

Thread w = Thread.currentThread();

SNode h = head;

int spins = (shouldSpin(s) ?

(timed ? maxTimedSpins : maxUntimedSpins) : 0);

for (;;) {

//當前線程被打斷嘗試取消s節點

if (w.isInterrupted())

s.tryCancel();

//獲取匹配數據,並返回

SNode m = s.match;

if (m != null)

return m;

//如果有超時處理,計算執行時間,超時執行取消

if (timed) {

long now = System.nanoTime();

nanos -= now - lastTime;

lastTime = now;

if (nanos <= 0) {

s.tryCancel();

continue;

}

}

if (spins > 0)

//判斷是否應該自旋

spins = shouldSpin(s) ? (spins-1) : 0;

else if (s.waiter == null)

s.waiter = w; // establish waiter so can park next iter

else if (!timed) 如果沒有超時處理調用 沒有超時參數的 LockSupport.park

LockSupport.park(this);

else if (nanos > spinForTimeoutThreshold)//如果剩餘等待時間大於最小的臨界值,執行有超時 參數的LockSupport.park

LockSupport.parkNanos(this, nanos);

}

}

clean

 

clean清除取消節點的流程

  1. 執行s.next跳過取消節點
  2. 執行head跳過取消節點
  3. 移動p忽略cancel節點

源碼中的入隊是從頭部插入,而圖中節點入隊的順序爲 n1,n2,s,n3,n4,n5.。灰色表已經出隊的節點。

結合代碼

void clean(SNode s) {

 

 

 

s.item = null; // forget item

s.waiter = null; // forget thread

SNode past = s.next;

//流程1

if (past != null && past.isCancelled())

past = past.next;

 

// Absorb cancelled nodes at head

//流程2

SNode p;

while ((p = head) != null && p != past && p.isCancelled())

casHead(p, p.next);// 這裏被清除的節點next沒有清理,GC 問題??

//流程3

// 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分析

構造方法

創建一個空的節點作爲和尾

TransferQueue() {

QNode h = new QNode(null, false); // initialize to dummy node.

head = h;

tail = h;

}

//QNode與SNode實現相似, 都有next,item,waiter

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; //標識是否是存數據

//

}

transfer分析

基本的算法循環中有以下兩種行爲

  1. 當出現隊列中節點爲空,或模式相同,作爲接受者入隊,等待傳遞者匹配成功後,返回匹配的數據。
  2. 當隊列中包含等待的節點,並且模式不相同,嘗試傳遞item給等待節點,並返回匹配節點的item

TransferQueue中transfer的實現比TransferQueue簡單,傳遞者不需要入隊。

Object transfer(Object e, boolean timed, long nanos) {

QNode s = null; // constructed/reused as needed

boolean isData = (e != null);

 

for (;;) {

QNode t = tail;

QNode h = head;

if (t == null || h == null) // saw uninitialized value

continue; // spin

 

if (h == t || t.isData == isData) { // empty or same-mode 行爲1

QNode tn = t.next;

if (t != tail) // inconsistent read //併發導致 tail改變

continue;

if (tn != null) { // lagging tail

advanceTail(t, tn);

continue;

}

if (timed && nanos <= 0) // can't wait 執行offer() 沒有配對 ,返回null

return null;

if (s == null)

s = new QNode(e, isData);//創建新節點

if (!t.casNext(null, s)) // failed to link in 把節點掛在尾部

continue;

 

advanceTail(t, s); // swing tail and wait 將尾部替換爲s節點

Object x = awaitFulfill(s, e, timed, nanos);//等待傳遞item

if (x == s) { // wait was cancelled //被取消 ,清除節點

clean(t, s);//清除s和其他被取消的節點

return null;

}

 

if (!s.isOffList()) { // not already unlinked

advanceHead(t, s); // unlink if head

if (x != null) // and forget fields

s.item = s;

s.waiter = null;

}

return (x != null) ? x : e;

 

} else { // complementary-mode 行爲2

QNode m = h.next; // node to fulfill 獲取到要匹配的節點

if (t != tail || m == null || h != head) //發生數據不一致

continue; // inconsistent read

 

Object x = m.item;

if (isData == (x != null) || // m already fulfilled m已經被匹配

 

x == m || // m cancelled //被取消

!m.casItem(x, e)) { // lost CAS // 傳遞item給接受者,然後判斷是否成功

advanceHead(h, m); // 出隊並重試

continue;

}

//匹配成功,head出列

advanceHead(h, m); // successfully fulfilled

LockSupport.unpark(m.waiter); //釋放等待的m

return (x != null) ? x : e;

}

}

}

 

 

awaitFulfill  與TransferStack實現邏輯一致,不同在於 最後獲取的是 item。這個方法就不詳細講解了。

總結:使用雙重棧和雙重隊列實現, 取數據和放數據 都可以作爲接受者和傳遞者,以雙重棧爲例,執行時發現 隊列中沒有節點或第一個節點的模式相同 就作爲接受者入隊等待,發現隊列中第一節點模式不同 作爲傳遞者入隊,將數據實例通過CAS傳遞給 接受者,然後解除接受者的阻塞。實現過程中用到了優化阻塞和併發的一系列策略,要注意理解和總結。

 

參考資料:

jdk中 SynchronousQueue的演變過程

http://ifeve.com/java-synchronousqueue/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章