深入浅出SynchronousQueue队列(一)

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

概述

synchronousQueue是一个没有数据缓冲的阻塞队列,生产者线程的插入操作put()必须等待消费者的删除操作take(),反过来也一样。和其他阻塞队列不同之处在于,内部类将入队出队操作统一封装成了一个接口实现,内部类数据保存的是每个操作动作,比如put操作,保存插入的值,并根据标识来判断是入队还是出队操作,如果是take操作,则值为null,通过标识符能判断出来是出队操作。SynchronousQueue分为公平策略(FIFO)和非公平策略(LIFO),两种策略分别对应其两个内部类实现,公平策略使用队列结构实现,非公平策略使用栈结构实现。默认情况下采用非公平性访问策略,当然也可以通过构造函数来设置为公平性访问策略(为true即可)。

继承关系

public class SynchronousQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable
复制代码

780971464-5d7360fbcce3a_fix732.png

重要属性

    // cpu数量,会在自旋控制时使用
    static final int NCPUS = Runtime.getRuntime().availableProcessors();

    // 自旋次数,指定了超时时间时使用,这个常量配合CAS操作使用,相当于循环次数
    // 当CPU数量小于2的时候不自旋
    static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;

    // 自旋次数,未指定超时时间时使用
    static final int maxUntimedSpins = maxTimedSpins * 16;

    // 自旋超时时间阈值,在设置的时间超过这个时间时以这个时间为准,单位,纳秒
    static final long spinForTimeoutThreshold = 1000L;
    
    // 后进先出队列和先进先出队列
    @SuppressWarnings("serial")
    static class WaitQueue implements java.io.Serializable { }
    static class LifoWaitQueue extends WaitQueue {
        private static final long serialVersionUID = -3633113410248163686L;
    }
    static class FifoWaitQueue extends WaitQueue {
        private static final long serialVersionUID = -3623113410248163686L;
    }
    // 序列化操作使用
    private ReentrantLock qlock;
    private WaitQueue waitingProducers;
    private WaitQueue waitingConsumers;
    
    // 所有的队列操作都通过transferer来执行,统一方法执行
    // 初始化时会根据所选的策略实例化对应的内部实现类
    private transient volatile Transferer<E> transferer;
复制代码

SynchronousQueue没有设置变量来保存入队出队操作的数据,操作方法都在Transferer中。

构造方法

构造方法很清晰,根据所选的策略实现对应的Transferer内部接口实现类来进行队列操作

    //默认构造方法的线程等待队列是不保证顺序的
    public SynchronousQueue() {
        this(false);
    }

    //如果fair为true,那SynchronousQueue所采用的是能保证先进先出的TransferQueue,也就是先被挂起的线程会先返回
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }
复制代码

抽象类Transferer

TransferQueue的父类Transferer比较简单,就是一个transfer方法,需要子类具体实现.

//e:不为空则是put操作,生产者入队操作,需要消费者出队操作来获取入队的值;为空,则是消费者
timed:操作是否设置超时
nanos:超时时间,单位:纳秒
返回值:非空则表明操作成功,返回消费的item或生产的item;空则表明由于超时或中断引起操作失败。
abstract static class Transferer<E> {
    abstract E transfer(E e, boolean timed, long nanos);
}
复制代码

重要方法

put/offer

入队操作通过内部类调用transfer

    //向SynchronousQueue中添加数据,如果此时线程队列中没有获取数据的线程的话,当前的线程就会挂起等待
    public void put(E e) throws InterruptedException {
        //添加的数据不能是null
        if (e == null) throw new NullPointerException();
        //可以看到添加的方法调用的是transfer方法,如果添加失败会抛出InterruptedException异常
        //这里相当于继续把线程中断的InterruptedException向上抛出
        if (transferer.transfer(e, false, 0) == null) {
            Thread.interrupted();
            throw new InterruptedException();
        }
    }
    
    //如果此时没有线程正在等待获取数据的话transfer就会返回null,即添加数据失败
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        return transferer.transfer(e, true, 0) != null;
    }
    
    //带超时时间的offer方法,此方法会等待一个超时时间,如果时间过了还没有线程来获取数据就会返回失败
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        if (e == null) throw new NullPointerException();
        //添加的数据被其他线程成功获取,返回成功
        if (transferer.transfer(e, true, unit.toNanos(timeout)) != null)
            return true;
        //如果添加数据失败了,有可能是线程被中断了,不是的话直接返回false
        if (!Thread.interrupted())
            return false;
        //是线程被中断的话就向上跑出InterruptedException异常
        throw new InterruptedException();
    }
复制代码

take/poll

出队操作通过内部类调用transfer,入队元素e为null

    //take方法用于从队列中取数据,如果此时没有添加数据的线程被挂起,那当前线程就会被挂起等待
    public E take() throws InterruptedException {
        E e = transferer.transfer(null, false, 0);
        //成功获取数据
        if (e != null)
            return e;
        //没有获取到数据,同时又退出挂起状态了,那说明线程被中断了,向上抛出InterruptedException
        Thread.interrupted();
        throw new InterruptedException();
    }
    
    //poll方法同样用于获取数据
    public E poll() {
        return transferer.transfer(null, true, 0);
    }
    
    //带超时时间的poll方法,如果超时时间到了还没有线程插入数据,就会返回失败
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E e = transferer.transfer(null, true, unit.toNanos(timeout));
        //返回结果有2种情况
        //e != null表示成功取到数据了
        //!Thread.interrupted()表示返回失败了,且是因为超时失败的,此时e是null
        if (e != null || !Thread.interrupted())
            return e;
        //返回失败了,并且是因为当前线程被中断了
        throw new InterruptedException();
    }
复制代码

其他方法以空队列为标准进行处理,比如队列长度直接返回0,判空返回true。

总结

SynchronousQueue适合做交换工作,例如生产者的线程和消费者的线程同步以传递某些信息、事件或者任务。其特点如下:

内部实现主要在于两个内部实现类,要注意内部类中节点保存的不是数据而是每次操作这个动作;

通过封装的一个接口transfer来完成入队和出队操作;

Guess you like

Origin juejin.im/post/7034886533488312327