大飛老師帶你看線程(併發容器-SynchronousQueue)下

本文作者:王一飛,叩丁狼高級講師。原創文章,轉載請註明出處。

接上一篇, 本篇講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個節點。如果配對失敗,從頭循環。
在這裏插入圖片描述

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