这是我参与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
复制代码
重要属性
// 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来完成入队和出队操作;