本文作者:王一飛,叩丁狼高級講師。原創文章,轉載請註明出處。
接上一篇, 本篇講SynchronousQueue隊列非公平策略put與take操作
#####源碼分析
2:非公平鎖策略- put / take
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
if (transferer.transfer(e, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
public E take() throws InterruptedException {
E e = transferer.transfer(null, false, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
put 跟 take 方法有都調用:
調用transfer 方法:
put : transferer.transfer(e, false, 0)
take: transferer.transfer(null, false, 0);
從前面內部結構看: SynchronousQueue 非公平策略底層實際上委託給TransferStack 棧實現, 而內部存儲數據使用SNode 棧節點 來看下節點源碼:
static final class SNode {
volatile SNode next; //下一個節點
volatile SNode match; //相匹配的節點
volatile Thread waiter; //線程掛起與喚醒控制: park /unpark
Object item; //節點內容
//模式控制變量
//非公平策略模式有3種:
//REQUEST:表示消費數據-take操作
//DATA:表示生產數據-put操作
//FULFILLING:介於上2個狀態間
//多一個狀態原因:TransferStack 配對操作原理是:每個線程配對時,都會先加入到棧頂中, 不管是take還是put,如果此時發現入棧的線程跟棧內線程可以配對,那麼此時線程可以使用FULFILLING狀態類標記:將要執行配對邏輯線程, 提示其他配對線程,配對時跳過這個節點,匹配其他的線程。
int mode;
//cas原子操作, 設置next節點
boolean casNext(SNode cmp, SNode val) {...}
//節點匹配,匹配成功,則unpark等待線程
boolean tryMatch(SNode s) {...}
//取消節點, 將節點內容設置爲自己
void tryCancel() {...}
//判斷是否操作結束
boolean isCancelled() {...}
}
此處研究的是公平鎖策略, 所以, 此時的transfer變量執項的是: TransferStack 類的實例
E transfer(E e, boolean timed, long nanos) {
SNode s = null;
//根據transfer方法參數e判斷當前操作模式
//有值DATA 反之爲REQUEST
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
SNode h = head; //初始時head爲null
//第一次put或take h==null成立, 如果已經存在掛起線程,入棧, 不管take或put mode必須一致纔可以進入,此分支是入棧分支,不會進行配對
if (h == null || h.mode == mode) {
if (timed && nanos <= 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) {
clean(s); //一旦配對成功, 對應的線程出棧(被清除)
return null;
}
if ((h = head) != null && h.next == s)
casHead(h, s.next);
return (E) ((mode == REQUEST) ? m.item : s.item); //返回結果
}
//出現mode不同操作時,那麼就需要配對了,此時需要滿足單前棧頂節點不是正在配對節點, 所以做了排除其他也企圖對棧頂節點配對的競爭線程操作
} else if (!isFulfilling(h.mode)) {
if (h.isCancelled()) //檢查,如果棧頂節點配其他線程配對成功,棧頂節點下移
casHead(h, h.next);
//如果沒有競爭線程, 線程入棧成爲棧頂節點, 同時加正在配對狀態
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) {
//獲取目標配對的節點
SNode m = s.next;
//如果目標配對節點爲null, 則表示目標配對節點被其他線程搶走了,
if (m == null) {
casHead(s, null); //從新移動棧頂節點爲null
s = null; //配對失敗, 跳出循環, 從新循環判斷
break;
}
//如果不爲null, 獲取目標配對節點下一個節點
SNode mn = m.next;
if (m.tryMatch(s)) { //嘗試配對
casHead(s, mn); //配對成功,移動棧頂節點到mn, 同時配對的2個節點同時出棧, 返回結果
return (E) ((mode == REQUEST) ? m.item : s.item);
} else
//如果配對失敗,尋找原目標節點下一個節點,重新循環配對
s.casNext(m, mn);
}
}
} else { //進入這個分支,表示目標配對的節點是一個正在配對的節點
//直接跳過,配對下一個節點 , 接下邏輯跟上面配對一樣
SNode m = h.next;
if (m == null)
casHead(h, null);
else {
SNode mn = m.next;
if (m.tryMatch(h))
casHead(h, mn);
else
h.casNext(m, mn);
}
}
}
}
非公平策略操作流程圖:
這裏需要說明, 如果元素過多,併發時,配對的線程具體要配對誰,那麼就隨機啦,無法按照圖中理想的配對順序。這也是爲什麼說是非公平策略啦。
總結:
結合代碼: transferer執行流程可歸納爲以下:
1: transferer調用transfer方法實現SynchronousQueue 公平隊列的take跟put操作
2:不管是take或者put 線程只要跟棧頂節點模式一樣,都要入棧,然後通過自旋方式尋找匹配,找不到配對掛起。如果時間消耗完畢,或者被取消,直接出列。
3:如果找到配對的節點,將當前線程打上FULFILLING標記,嘗試配對。如果是此時有競爭配對線程,檢查到該節點有FULFILLING標記,直接跳過,尋找下一個配對節點。
4:如果配對成功, 彈出當前配對成功2個節點。如果配對失敗,從頭循環。