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

posted @ 2020-06-08 23:23  蓝天随笔  阅读(3006)  评论(0编辑  收藏  举报