SynchronousQueue

SynchronousQueue

無緩沖阻塞隊列,用來在兩個線程之間移交元素

模式相同則入棧(隊),不同則出棧(隊),所以并非真正的無緩沖

隊列為空也入棧(隊)

并不是真正的隊列,不維護存儲空間,維護的是一組線程,這些線程在等待著放入或者移出元素

這種阻塞隊列確實是非常復雜的,但是卻非常有用。SynchronousQueue是一種極為特殊的阻塞隊列,它沒有實際的容量,任意線程(生產者線程或者消費者線程,生產類型的操作比如put,offer,消費類型的操作比如poll,take)都會等待知道獲得數據或者交付完成數據才會返回,一個生產者線程的使命是將線程附著著的數據交付給一個消費者線程,而一個消費者線程則是等待一個生產者線程的數據。它們會在匹配到互斥線程的時候就會做數據交易,比如生產者線程遇到消費者線程時,或者消費者線程遇到生產者線程時,一個生產者線程就會將數據交付給消費者線程,然后共同退出。在java線程池newCachedThreadPool中就使用了這種阻塞隊列。

優點

將更多關于任務狀態的信息反饋給生產者。當交付被接受時,它就知道消費者已經得到了任務,而不是簡單地把任務放入一個隊列——這種區別就好比將文件直接交給同事,還是將文件放到她的郵箱中并希望她能盡快拿到文件。

成員變量

// CPU的數量
static final int NCPUS = Runtime.getRuntime().availableProcessors();
// 有超時的情況自旋多少次,當CPU數量小于2的時候不自旋
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;
// 沒有超時的情況自旋多少次
static final int maxUntimedSpins = maxTimedSpins * 16;
// 針對有超時的情況,自旋了多少次后,如果剩余時間大于1000納秒就使用帶時間的LockSupport.parkNanos()這個方法
static final long spinForTimeoutThreshold = 1000L;
// 傳輸器,即兩個線程交換元素使用的東西
private transient volatile Transferer<E> transferer;
//主要定義了一個transfer方法用來傳輸元素
abstract static class Transferer<E> {
    abstract E transfer(E e, boolean timed, long nanos);
}
// 以棧方式實現的Transferer
static final class TransferStack<E> extends Transferer<E> {
    // 棧中節點的幾種類型: 
    static final int REQUEST    = 0;// 1. 消費者(請求數據的)
    static final int DATA       = 1;// 2. 生產者(提供數據的)
    static final int FULFILLING = 2;// 3. 二者正在匹配中
    // 棧中的節點
    static final class SNode {
        volatile SNode next;        // 下一個節點
        volatile SNode match;      // 匹配者     
        volatile Thread waiter;     // 等待著的線程      
        Object item;                // 元素  
        int mode;//也就是節點的類型,是消費者,是生產者,還是正在匹配中
    }
    volatile SNode head;// 棧的頭節點
}
// 以隊列方式實現的Transferer
static final class TransferQueue<E> extends Transferer<E> {
    // 隊列中的節點
    static final class QNode {
        volatile QNode next;          // 下一個節點
        volatile Object item;         // 存儲的元素   
        volatile Thread waiter;       // 等待著的線程
        final boolean isData;// 是否是數據節點
    }
    transient volatile QNode head;// 隊列的頭節點
    transient volatile QNode tail;// 隊列的尾節點
}

構造器

public SynchronousQueue() {
    this(false);// 默認非公平模式
}
public SynchronousQueue(boolean fair) {
    transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();// 公平模式使用隊列,非公平模式使用棧
}

入隊

public void put(E e) throws InterruptedException {  
    if (e == null) throw new NullPointerException();// 元素不可為null
    // 三個參數分別是:傳輸的元素,是否需要超時,超時的時間
    if (transferer.transfer(e, false, 0) == null) {
        // 如果傳輸失敗,直接讓線程中斷并拋出中斷異常
        Thread.interrupted();
        throw new InterruptedException();
    }
}

出隊

public E take() throws InterruptedException {
    // 第一個參數為null表示是消費者,要取元素
    E e = transferer.transfer(null, false, 0);
    if (e != null)// 如果取到了元素就返回
        return e;
    // 否則讓線程中斷并拋出中斷異常
    Thread.interrupted();
    throw new InterruptedException();
}

棧的transfer

E transfer(E e, boolean timed, long nanos) {
    SNode s = null; 
    int mode = (e == null) ? REQUEST : DATA;// 根據e是否為null決定是生產者還是消費者
    for (;;) {// 自旋
        SNode h = head;// 棧頂元素 
        if (h == null || h.mode == mode) {// 入棧 
            if (timed && nanos <= 0) {     // 如果有超時設置而且已到期,不能再入棧,協助清理cancel狀態的元素
                if (h != null && h.isCancelled())// 如果頭節點不為空且是取消狀態
                    casHead(h, h.next);//頭節點彈出,將h.next設置為新的head,并進入下一次循環
                else  
                    return null;// 否則,直接返回null(超時返回null)
            } else if (casHead(h, s = snode(s, e, h, mode))) {// 入棧成功      
                // 調用awaitFulfill()方法自旋+阻塞當前入棧的線程并等待被匹配到
                SNode m = awaitFulfill(s, timed, nanos);
                // 如果m等于s,說明取消了,那么就把它清除掉,并返回null
                if (m == s) {               
                    clean(s);
                    return null;// 被取消了返回null
                }
                
                // 到這里說明匹配到元素了,因為從awaitFulfill()里面出來要不被取消了要不就匹配到了,如果頭節點不為空,并且頭節點的下一個節點是s,就把頭節點換成s的下一個節點,也就是把h和s都彈出了,也就是把棧頂兩個元素都彈出了
                if ((h = head) != null && h.next == s)
                    casHead(h, s.next);     
                // 根據當前節點的模式判斷返回m還是s中的值
                return (E) ((mode == REQUEST) ? m.item : s.item);
            }
        } else if (!isFulfilling(h.mode)) {     
            if (h.isCancelled())// 節點和當前節點模式不一樣,如果頭節點不是正在匹配中并且已經取消了,就把它彈出棧         
                casHead(h, h.next);         
            else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                // 頭節點沒有在匹配中,就讓當前節點先入隊,再讓他們嘗試匹配
                // 且s成為了新的頭節點,它的狀態是正在匹配中
                for (;;) { 
                    SNode m = s.next;       
                    // 如果m為null,說明除了s節點外的節點都被其它線程先一步匹配掉了
                    // 就清空棧并跳出內部循環,到外部循環再重新入棧判斷
                    if (m == null) {        
                        casHead(s, null);   
                        s = null;           
                        break;            
                    }
                    SNode mn = m.next;
                    // 如果m和s嘗試匹配成功,就彈出棧頂的兩個元素m和s
                    if (m.tryMatch(s)) {
                        casHead(s, mn);     
                        // 返回匹配結果
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    } else                 
                        // 嘗試匹配失敗,說明m已經先一步被其它線程匹配了,就協助清除它
                        s.casNext(m, mn);   
                }
            }
        } else {                            
            //當前節點和頭節點模式不一樣,且頭節點是正在匹配中
            SNode m = h.next;              
            if (m == null)                 
                // 如果m為null,說明m已經被其它線程先一步匹配了
                casHead(h, null);           
            else {
                SNode mn = m.next;
                // 協助匹配,如果m和s嘗試匹配成功,就彈出棧頂的兩個元素m和s
                if (m.tryMatch(h))          
                    // 將棧頂的兩個元素彈出后,再讓s重新入棧
                    casHead(h, mn);         
                else                        
                    // 嘗試匹配失敗,說明m已經先一步被其它線程匹配了
                    // 就協助清除它
                    h.casNext(m, mn);       
            }
        }
    }
}
// 三個參數:需要等待的節點,是否需要超時,超時時間
//等待其他的線程來匹配,這個線程一直阻塞直到被匹配,在阻塞之前首先會自旋,這個自旋會在阻塞之前進行,它會調用shouldSpin方法來進行判斷是否需要自選
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 (;;) {
        if (w.isInterrupted())// 當前線程中斷了,嘗試清除s
            s.tryCancel();
        
        // 檢查s是否匹配到了元素m(有可能是其它線程的m匹配到當前線程的s)
        SNode m = s.match;
        if (m != null)// 如果匹配到了,直接返回m
            return m;
        
        // 如果需要超時
        if (timed) {
            // 檢查超時時間如果小于0了,嘗試清除s
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                s.tryCancel();
                continue;
            }
        }
        if (spins > 0)
            // 如果還有自旋次數,自旋次數減一,并進入下一次自旋
            spins = shouldSpin(s) ? (spins-1) : 0;
        
        // 后面的elseif都是自旋次數沒有了
        else if (s.waiter == null)
            // 如果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);
    }
}
// SNode里面的方向,調用者m是s的下一個節點
// 這時候m節點的線程應該是阻塞狀態的
boolean tryMatch(SNode s) {
    // 如果m還沒有匹配者,就把s作為它的匹配者
    if (match == null &&
        UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
        Thread w = waiter;
        if (w != null) {    
            waiter = null;
            // 喚醒m中的線程,兩者匹配完畢
            LockSupport.unpark(w);
        }
        // 匹配到了返回true
        return true;
    }
    // 可能其它線程先一步匹配了m,返回其是否是s
    return match == s;
}

如果當前的交易棧是空的,或者包含與請求交易節點模式相同的節點,那么就將這個請求交易的節點作為新的棧頂節點,等待被下一個請求交易的節點匹配,最后會返回匹配節點的數據或者null,如果被取消則會返回null。

如果當前交易棧不為空,并且請求交易的節點和當前棧頂節點模式互補,那么將這個請求交易的節點的模式變為FULFILLING,然后將其壓入棧中,和互補的節點進行匹配,完成交易之后將兩個節點一起彈出,并且返回交易的數據。

如果棧頂已經存在一個模式為FULFILLING的節點,說明棧頂的節點正在進行匹配,那么就幫助這個棧頂節點快速完成交易,然后繼續交易。

隊列的transfer

E transfer(E e, boolean timed, long nanos) {  
    
 //在每一種情況,執行的過程中,檢查和嘗試幫助其他stalled/slow線程移動隊列頭和尾節點 循環開始,首先進行null檢查,防止未初始隊列頭和尾節點。當然這種情況,在當前同步隊列中,不可能發生,如果調用持有transferer的non-volatile/final引用, 可能出現這種情況。一般在循環的開始,都要進行null檢查,檢查過程非常快,不用過多擔心性能問題。 
         
    QNode s = null; 
    //如果元素e不為null,則為DATA模式,否則為REQUEST模式  
    boolean isData = (e != null);  
    for (;;) {  
        QNode t = tail;  
        QNode h = head;  
        //如果隊列頭或尾節點沒有初始化,則自旋  
        if (t == null || h == null)           
            continue;                       
        if (h == t || t.isData == isData) { //如果隊列為空,或當前節點與隊尾模式相同 ,入隊 
            QNode tn = t.next;  
            if (t != tail)                  //如果t不是隊尾,非一致性讀取,自旋 
                continue;  
            if (tn != null) {               //tn不為null,說明有其他線程添加了tn結點  (設置了tail.next)           
                advanceTail(t, tn);  //如果t.next不為null,設置新的隊尾,自旋  
                continue;  
            }  
            if (timed && nanos <= 0) //如果超時,且超時時間小于0,則返回null  
                return null;  
            if (s == null)  
                s = new QNode(e, isData);  //根據元素和模式構造節點
            if (!t.casNext(null, s))        // 新節點入隊列失敗(t.next被賦值了),自旋
                continue;
            //設置隊尾為當前節點  
            advanceTail(t, s);              // swing tail and wait  
            //自旋或阻塞直到節點被fulfilled  
            Object x = awaitFulfill(s, e, timed, nanos);  
            if (x == s) {                   // wait was cancelled  
                //如果s指向自己,s出隊列,并清除隊列中取消等待的線程節點  
                clean(t, s);  
                return null;  
            }  
            if (!s.isOffList()) {           // s仍然在隊列中 
                advanceHead(t, s);          
                if (x != null)              
                    s.item = s;  
                s.waiter = null;  
            }  
            //如果自旋等待匹配的節點元素不為null,則返回x,否則返回e  
            return (x != null) ? x : e;  
        } else {                              
            //如果隊列不為空,且與隊頭的模式不同,及匹配成功 (與隊尾匹配成功,則一定與隊頭匹配成功!) 
            QNode m = h.next;                
            if (t != tail || m == null || h != head)  
                //如果h不為當前隊頭,則返回,即讀取不一致  
                continue;                   
            Object x = m.item;  
            if (
                isData == (x != null) ||   
                x == m ||                    
                !m.casItem(x, e)   
            ){        
                
                advanceHead(h, m);          //如果隊頭后繼,取消等待,則出隊列  
                continue;  
            }  
            //否則匹配成功  
            advanceHead(h, m);                
            //unpark等待線程  
            LockSupport.unpark(m.waiter);  
            //如果匹配節點元素不為null,則返回x,否則返回e,即take操作,返回等待put線程節點元素,  
            //put操作,返回put元素  
            return (x != null) ? x : e;  
        }  
    }  
}

如果隊列為空,或者請求交易的節點和隊列中的節點具有相同的交易類型,那么就將該請求交易的節點添加到隊列尾部等待交易,直到被匹配或者被取消

如果隊列中包含了等待的節點,并且請求的節點和等待的節點是互補的,那么進行匹配并且進行交易

SynchronousQueue一般用于生產、消費的速度大致相當的情況,這樣才不會導致系統中過多的線程處于阻塞狀態。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容