SynchronousQueue也是一个队列,与其他队列不同的是,SynchronousQueue队列中,他不存储数据,存储生产者或者是消费者,让生产者和消费者直接交换手中的数据,任何一个写操作必须等到一个读操作(反之亦然),交换成功才是成功,否则就阻塞或者返回失败(取决于调用的方法)。
生产者最终会有几种结果:
- 如果在阻塞期间有消费者来匹配,生产者就会将绑定的消息交给消费者
- 生产者得等阻塞结果,或者不允许阻塞,那么就直接失败
- 生产者在阻塞期间,如果线程中断,直接告辞。
同理,消费者和生产者的效果是一样。
使用例子
public static void main(String[] args) throws ExecutionException, InterruptedException {
SynchronousQueue synchronousQueue = new SynchronousQueue();
Thread thread = new Thread(() -> {
try {
synchronousQueue.put("消息");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
thread.start();
Thread.sleep(1000);
System.out.println("获取数据:"+synchronousQueue.poll());
}
1、内部类结构
抽象类Transferer 提供了transfer方法,生产者或者消费者最终调用的都是这个方法
底层的数据结构提供了两种,队列和栈(TransferQueue,TransferStack)
生产者调用transfer方法的时候,第一个参数e就是需要传递的数据
消费者调用transfer方法的时候,第一个参数e为null
abstract static class Transferer<E> {
abstract E transfer(E e, boolean timed, long nanos);
}
static final class TransferStack<E> extends Transferer<E> {
...
}
static final class TransferQueue<E> extends Transferer<E> {
...
}
SynchronousQueue针对抽象类Transferer做了2种实现。
- TransferStack
- TransferQueue
这两种类继承了Transferer抽象类,在构建SynchronousQueue时,会指定使用哪种子类
// 到底采用哪种实现,需要把对应的对象存放到这个属性中
private transient volatile Transferer<E> transferer;
// 采用无参时,会调用下述方法,再次调用有参构造传入false
public SynchronousQueue() {
this(false);
}
// 调用的是当前的有参构造,fair代表公平还是不公平
public SynchronousQueue(boolean fair) {
// 如果是公平,采用Queue(先进先出),如果是不公平,采用Stack(后进先出)
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
2、源码
2.1 QNode
static final class QNode {
// 下一个节点
volatile QNode next;
// item在不同情况下效果不同
// 生产者:有数据
// 消费者:为null
volatile Object item;
// 当前线程
volatile Thread waiter;
// 当前属性是区分消费者和生产者的属性
final boolean isData;
// 最终生产者需要将item交给消费者
// 最终消费者需要获取生产者的item
...
}
2.2 transfer方法实现
// 当前方法是TransferQueue的核心内容
// e:传递的数据
// timed:false,代表无限阻塞,true,代表阻塞nacos时间
E transfer(E e, boolean timed, long nanos) {
// 当前QNode是要封装当前生产者或者消费者的信息
QNode s = null;
// isData == true:代表是生产者
// isData == false:代表是消费者
boolean isData = (e != null);
// 死循环
for (;;) {
// 队列头尾
QNode t = tail;
QNode h = head;
// 为了避免TransferQueue还没有初始化,这边做一个健壮性判断
if (t == null || h == null)
continue;
// h == t 头尾相等,说明是第一个节点
// t.isData == isData 如果有节点,同时当前节点和队列节点属于同一种角色
// if中的逻辑是进到队列
if (h == t || t.isData == isData) {
// 获取尾节点的下一个元素 避免并发操作
QNode tn = t.next;
// 如果t和现在的tail不相等,说明有人操作了队列并添加了新的元素
if (t != tail)
// 重新走for循环
continue;
// 如果t==tail,但是tn为不null,说明前面有线程并发,添加了一个节点,
// 但是还没来得及修改tail,那么我就帮他修改 修改完我再重新进来
if (tn != null) {
advanceTail(t, tn);
continue;
}
// 获取当前线程是否可以阻塞
// 如果timed为true,并且阻塞的时间小于等于0
// 不需要匹配,直接告辞
if (timed && nanos <= 0)
return null;
// 如果可以阻塞,将当前需要插入到队列的QNode构建出来
if (s == null)
s = new QNode(e, isData);
// 基于CAS操作,将tail节点的next设置为当前节点
if (!t.casNext(null, s))
// 修改失败则重来
continue;
// CAS操作成功,直接替换tail的指向
advanceTail(t, s);
// 如果进到队列中了,挂起线程,要么等生产者,要么等消费者。
// x是返回替换后的数据
Object x = awaitFulfill(s, e, timed, nanos);
// 如果x的值等于s,说明节点取消了
if (x == s) {
// 清空取消节点,返回null
clean(t, s);
return null;
}
// 交换完成 判断当前节点是否还在队列中
if (!s.isOffList()) {
// 将当前节点设置为head
advanceHead(t, s);
// 如果 x != null, 如果拿到了数据,说明我是消费者
if (x != null)
// 将当前节点的item置为自己
s.item = s;
// 将节点中的线程置为null
s.waiter = null;
}
// 返回数据
return (x != null) ? (E)x : e;
} else {
// 匹配队列中的角色
// 拿到head的next,作为要匹配的节点
QNode m = h.next;
// 做并发判断,如果头节点,尾节点,或者head.next发生了变化,这边要重新走for循环
if (t != tail || m == null || h != head)
continue;
// 没并发问题,可以拿数据
// 拿到m节点的item作为x。
Object x = m.item;
// isData == (x != null) 表示匹配的角色与当前角色一致(消费者匹配消费者,生产者匹配生产者)
if (isData == (x != null) ||
// x == m 如果排队的节点取消,就会将当前QNode中的item指向QNode
x == m ||
// 如果前面两个都没满足,可以交换数据了。
// 如果交换失败,说明有并发问题,
!m.casItem(x, e)) {
//更换头节点 重新匹配
advanceHead(h, m);
continue;
}
// 替换head
advanceHead(h, m);
// 唤醒head.next中的线程
LockSupport.unpark(m.waiter);
// 这边匹配好了,数据也交换了,直接返回
// 如果 x != null,说明队列中是生产者,当前是消费者,这边直接返回x具体数据
// 反之,队列中是消费者,当前是生产者,直接返回自己的数据
return (x != null) ? (E)x : e;
}
}
}