1 SynchronousQueue的介绍与应用
SynchronousQueue队列虽然实现了BlockingQueue接口,但它并不是传统意义上的队列,SynchronousQueue的长度是0,内部不存储元素,使用内部类Transferer定义了数据的传输格式,在一个生产者调用put方法存数据到SynchronousQueue后,必须等待消费者调用take拿走这个数据才可以。反之亦然。
- 简单使用
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<User> queue = new SynchronousQueue<>();
new Thread(() -> {
User user = new User();
user.setName("jack&rose");
System.out.println("publisher:" + user);
try {
queue.put(user);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
Object user = queue.take();
System.out.println("consumer:" + user);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
2 SynchronousQueue源码分析
2.1 Transferer<E>
Transferer<E>是SynchronousQueue内部的一个抽象类
abstract static class Transferer<E> {
// transfer抽象方法,定义了数据的传输方式
// timed表示本次传输是否需要等待,nanos表示要等待的时间(纳秒)
// 生产者调用transfer方法,需要传递第一个参数,也就是数据
// 消费者调用transfer方法,第一个参数传递为null,代表获取数据
abstract E transfer(E e, boolean timed, long nanos);
}
Transferer有两个实现,分别对应了SynchronousQueue的公平操作和不公平操作
- TransferQueue代表公平处理方式
- TransferStack代表不公平处理方式
2.2 构造器
SynchronousQueue内部提供公平和非公平两种Transferer的实现,通过构造器确定使用哪一种,默认使用非公平的TransferStack
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
2.3 TransferQueue源码分析
TransferQueue和TransferStack源码非常相似
- TransferStack是后进来的线程放到了头部,会先进行匹配。
- TransferQueue匹配的时候都是拿的最前面进来的节点返回数据
这里只看TransferQueue的源码,理解transfer即可
2.3.1 QNode
static final class QNode {
// 当前节点可以获取到next节点
volatile QNode next;
// item在不同情况下效果不同
// 生产者:有数据
// 消费者:为null
volatile Object item;
// 当前线程
volatile Thread waiter;
// 当前属性是永磊区分消费者和生产者的属性
final boolean isData;
// 最终生产者需要将item交给消费者
// 最终消费者需要获取生产者的item
// 省略了大量提供的CAS操作
....
}
2.3.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 条件,说明当前队列没有生产者或者消费者,为空
// 如果有节点,同时当前节点和队列节点属于同一种角色。
// if中的逻辑是进到队列
if (h == t || t.isData == isData) {
// ===================在判断并发问题==========================
// 拿到尾节点的next
QNode tn = t.next;
// 如果t不为尾节点,进来说明有其他线程并发修改了tail
if (t != tail)
// 重新走for循环
continue;
// tn如果为不null,说明前面有线程并发,添加了一个节点
if (tn != null) {
// 直接帮助那个并发线程修改tail的指向
advanceTail(t, tn);
// 重新走for循环
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))
// 如果进到if,说明修改失败,重新执行for循环修改
continue;
// CAS操作成功,直接替换tail的指向
advanceTail(t, s);
// 如果进到队列中了,挂起线程,要么等生产者,要么等消费者。
// x是返回替换后的数据
Object x = awaitFulfill(s, e, timed, nanos);
// 如果元素和节点相等,说明节点取消了
if (x == s) {
// 清空当前节点,将上一个节点的next指向当前节点的next,直接告辞
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) ||
// 如果排队的节点取消,就会讲当前QNode中的item指向QNode
x == m ||
// 如果前面两个都没满足,可以交换数据了。
// 如果交换失败,说明有并发问题,
!m.casItem(x, e)) {
// 重新设置head节点,并且再走一次循环
advanceHead(h, m);
continue;
}
// 替换head
advanceHead(h, m);
// 唤醒head.next中的线程
LockSupport.unpark(m.waiter);
// 这边匹配好了,数据也交换了,直接返回
// 如果 x != null,说明队列中是生产者,当前是消费者,这边直接返回x具体数据
// 反之,队列中是消费者,当前是生产者,直接返回自己的数据
return (x != null) ? (E)x : e;
}
}
}