SynchronousQueue是一个特殊的阻塞队列,生产者插入操作后只能等待消费者移除操作,它没有任何容量,甚至没有存储一个元素的容量。在SynchronousQueue中不能使用peek方法,因为元素只有被移除时才存在,只有消费者移除了元素生产者才能往队列中插入元素,当然更不能进行迭代。队列的头节点是第一个排队插入元素的线程,队列是不允许存储null的元素。生产者和消费者必须互相等待,这样经过一个元素的生产到消费的过程,然后一致离开。
SynchronousQueue有两种策略,一种是公平模式,使用队列来完成,一种是非公平模式,使用栈来完成,不论是栈或队列都是用链表来实现的 。看一下SynchronousQueue的创建:
SynchronousQueue<String> s = new SynchronousQueue<>();
SynchronousQueue<String> b = new SynchronousQueue<>(true);
接下来看一下它的构造方法
/**
* 默认无参构造
*/
public SynchronousQueue() {
this(false);
}
/**
* 指定是否使用公平模式
*/
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
默认是创建一个非公平模式,也可以实现公平模式,如果是公平模式的话,可以保证第一个队首的线程是等待时间最长的线程,这时可以把SynchronousQueue看作是一个FIFO队列。无论是队列或栈,它们都继承了Transferer类。
基本属性
/** CPU的数量 */
static final int NCPUS = Runtime.getRuntime().availableProcessors();
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;
static final int maxUntimedSpins = maxTimedSpins * 16;
static final long spinForTimeoutThreshold = 1000L;
这四个属性是用来设置自旋时间的。阻塞是一个非常消耗性能的操作,要进行线程之间上下文的切换,所以一般在阻塞之前进行自旋,不停的使用死循环进行检测,当然不能永远停在循环中,必须要设定时间限定,如果在时间限定内通过自旋完成了某种操作,那就不用阻塞提高了响应速度。如果超时要进行阻塞。
当然在竞争激烈的情况下,自旋的时间长点也是可以的,但是过长的自旋也会白白浪费CPU的时间,所以时间限定并没有一个准确的值,要根据不同的情况进行设定。
首先是获取CPU的数量,分两种情况,一种是设定了时间限的自旋,如果CPU的数量是1,那就不进行自旋,只有一个CPU在进行自旋就不能进行其他操作了,CPU的数量>=2,设定maxTimedSpins为32。另一种是没有设定时间限的自旋,如果CPU的数量>=2,把maxUntimeSpins设为32*16,如果CPU的数量为1,把maxUntimeSpins设为0。
spinForTimeoutThreshold是为了防止自定义的时间过长而设置的,单位是纳秒,如果设定的时间>这个值,那就把spinForTimeoutThreshold当作时间限。
private transient volatile Transferer<E> transferer;
SynchronousQueue内有两个内部类TransferQueue和TransferStack,它们都继承了Transfer类,transferer就是具体实现的引用,所有方法都要基于transferer去执行。
TransferStack
/* Modes for SNodes, ORed together in node fields */
/** Node represents an unfulfilled consumer */
static final int REQUEST = 0;
/** Node represents an unfulfilled producer */
static final int DATA = 1;
/** Node is fulfilling another unfulfilled DATA or REQUEST */
static final int FULFILLING = 2;
TransferStack的三种状态,REQUEST表示消费者,DATA表示生产者,FULFILLING表示匹配另一个生产者或者消费者。任何线程对TransferStack的操作都应该是这三种状态中的一种。
看一下它的内部类snode。
/** Node class for TransferStacks. */
static final class SNode {
volatile SNode next; // 栈中的下一个节点
volatile SNode match; // 相匹配的节点
volatile Thread waiter; // 当前节点代表的线程
Object item; // 具体的item值或者null
int mode; // 代表当前线程的模式
// item和mode字段不需要设置成volatile类型的,因为它们总是在写入之前,读操作之后
SNode(Object item) { // snode的构造方法
this.item = item;
}
}
// 创建一个snode节点
static SNode snode(SNode s, Object e, SNode next, int mode) {
if (s == null) s = new SNode(e);
s.mode = mode;
s.next = next;
return s;
}
继续看一下snode类的方法。
casNext就是设置当前节点的下一个节点。
tryCancel就是取消当前操作。
isCancelled就是把当前的match匹配变成this,如果当前节点的match是空说明当前节点的任务还没有完成,换成this匹配自己说明任务被取消。
重点是tryMatch方法。
boolean tryMatch(SNode s) {
if (match == null &&
UNSAFE.compareAndSwapObject(this, matchOffset, null,