SynchronousQueue理解


一、定義


可以理解為"配對"隊列

特點:

1、內部沒有存儲

2、阻塞隊列

3、發送或者消費線程會阻塞,只有有一對消費和發送線程匹配上,才同時退出。

4、配對有公平模式和非公平模式(默認)

     公平模式用隊列實現 ,每次從隊列head開始匹配

    非公平模式用棧實現,每次從棧頂開始匹配。

 

二、使用


代碼:

public class SynchronousQueueDemo {
    private static BlockingQueue<String> synchronousQueue = new SynchronousQueue();
    private static Runnable runnable1 = () -> {
        System.out.println(Thread.currentThread().getName()+":put thread start");
        try {
            synchronousQueue.put("value1");
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName()+":put thread end");
    };

    private static Runnable runnable2 = () -> {
        System.out.println(Thread.currentThread().getName()+":take thread start");
        try {
            System.out.println(Thread.currentThread().getName()+":take value ->" + synchronousQueue.take());
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName()+":take thread end");
    };
    public static void main(String[] args) throws InterruptedException {
        Thread putThread1 = new Thread(runnable1,"putThread1");
        Thread putThread2 = new Thread(runnable1,"putThread2");
        Thread takeThread1 = new Thread(runnable2,"takeThread1");
        putThread1.start();
        Thread.sleep(1000);
        putThread2.start();
        Thread.sleep(1000);
        System.out.println("2 put thread is blocking...");
        takeThread1.start();
    }
}

 

返回:

putThread1:put thread start
putThread2:put thread start
2 put thread is blocking...
takeThread1:take thread start
putThread2:put thread end
takeThread1:take value ->value1
takeThread1:take thread end

 

解析:

1、SynchronousQueue默認非公平模式

      takeThread1配對的是putThread2。

2、沒有配對時,線程是則塞的。

 

三、模式


公平模式下的模型:

公平模式下,底層實現使用的是TransferQueue這個內部隊列,它有一個head和tail指針,用於指向當前正在等待匹配的線程節點。

初始化時,TransferQueue的狀態如下:

 

 

接着我們進行一些操作:

1、線程put1執行 put(1)操作,由於當前沒有配對的消費線程,所以put1線程入隊列,自旋一小會后睡眠等待,這時隊列狀態如下:

 

 

2、接着,線程put2執行了put(2)操作,跟前面一樣,put2線程入隊列,自旋一小會后睡眠等待,這時隊列狀態如下:

 

 

3、這時候,來了一個線程take1,執行了
take操作,由於tail指向put2線程,put2線程跟take1線程配對了(一put一take),這時take1線程不需要入隊,但是請注意了,這時候,要喚醒的線程並不是put2,而是put1。為何?

大家應該知道我們現在講的是公平策略,所謂公平就是誰先入隊了,誰就優先被喚醒,我們的例子明顯是put1應該優先被喚醒。至於讀者可能會有一個疑問,明明是take1線程跟put2線程匹配上了,結果是put1線程被喚醒消費,怎么確保take1線程一定可以和次首節點(head.next)也是匹配的呢?其實大家可以拿個紙畫一畫,就會發現真的就是這樣的。


公平策略總結下來就是:隊尾匹配隊頭出隊。

執行后put1線程被喚醒,take1線程的 take()方法返回了1(put1線程的數據),這樣就實現了線程間的一對一通信,這時候內部狀態如下:

 

 

4、最后,再來一個線程take2,執行take操作,這時候只有put2線程在等候,而且兩個線程匹配上了,線程put2被喚醒,

take2線程take操作返回了2(線程put2的數據),這時候隊列又回到了起點,如下所示:

 

 

以上便是公平模式下,SynchronousQueue的實現模型。總結下來就是:隊尾匹配隊頭出隊,先進先出,體現公平原則。

 

非公平模式下的模型:

我們還是使用跟公平模式下一樣的操作流程,對比兩種策略下有何不同。非公平模式底層的實現使用的是TransferStack,

一個棧,實現中用head指針指向棧頂,接着我們看看它的實現模型:

1、線程put1執行 put(1)操作,由於當前沒有配對的消費線程,所以put1線程入棧,自旋一小會后睡眠等待,這時棧狀態如下:

 

 

2、接着,線程put2再次執行了put(2)操作,跟前面一樣,put2線程入棧,自旋一小會后睡眠等待,這時棧狀態如下:

 

 

3、這時候,來了一個線程take1,執行了take操作,這時候發現棧頂為put2線程,匹配成功,但是實現會先把take1線程入棧,然后take1線程循環執行匹配put2線程邏輯,一旦發現沒有並發沖突,就會把棧頂指針直接指向 put1線程

 

 

4、最后,再來一個線程take2,執行take操作,這跟步驟3的邏輯基本是一致的,take2線程入棧,然后在循環中匹配put1線程,最終全部匹配完畢,棧變為空,恢復初始狀態,如下圖所示:

 

 

可以從上面流程看出,雖然put1線程先入棧了,但是卻是后匹配,這就是非公平的由來。

 

參考:https://zhuanlan.zhihu.com/p/29227508


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM