TransferQueue接口与常规的Queue实现不太一样,因此我把它单独拎出来做介绍。
1. 类结构图
图中的类/接口主要来自两个包。
java.util包
- Queue接口
- AbstractQueue抽象类
java.util.concurrent包(juc)
- BlockingQueue 阻塞队列接口
- TransferQueue 接口
- LinkedTransferQueue
- SynchronousQueue
其中,Queue、AbstractQueue、BlockingQueue在前文都有所提及,不再赘述。
2. TransferQueue(传输队列接口)
TransferQueue接口继承自BlockingQueue接口,因此有常规阻塞队列的所有操作。
区别于常规的阻塞队列,提供了一种适用于“生产者”、“消费者”模式的传输队列。考虑以下几个场景:
- 容器为空,消费者先到并因为当前无数据而阻塞,此时生产者到达并生产数据,从而消费者能消费数据,返回“传输成功(true)”;——tryTransfer(E e)
- 容器为空,生产者先到并尝试生产数据,但当前没有消费者阻塞等待数据,生产者立马放弃传输,返回“传输失败(false)”; ——tryTransfer(E e)
- 容器为空,此时生产者到达并尝试生产数据,由于当前没有消费者,生产者(有限时间内)阻塞。在有限时间内如果有消费者到达接收数据,返回“传输成功”,否则返回“传输失败”。——tryTransfer(E e, long timeout, TimeUnit unit)
- 容器为空,此时生产者到达并尝试生产数据,由于当前没有消费者,生产者(无限)阻塞,直到消费者到达并消费数据。 ——transfer(E e)
以上提到的容器都可以理解成队列,生产就是“入队操作”,消费就是“出队操作”。
本接口提供的方法主要就是针对以上几种场景,接口源码如下:
/**
1. 一种生产者等待消费者接收元素的阻塞队列。
2. tryTransfer(Object) 非阻塞
3. tryTransfer(Object,long,TimeUnit) 有限时间阻塞
4. transfer(Object) 无限阻塞
5. @since 1.7
*/
public interface TransferQueue<E> extends BlockingQueue<E> {
/**
* 如果已经有一个消费者在等待接收,则立即传送给定的元素;否则,不传送元素,返回false
*/
boolean tryTransfer(E e);
/**
* 传送元素到消费者,如果当前没有等待接收的消费者,无限等待
*/
void transfer(E e) throws InterruptedException;
/**
* 如果给定时间内,有消费者等待接收元素,则传送元素并返回true;否则,不传送元素,返回false
*/
boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException;
/**
* 判断是否“至少有一个消费者在等待接收元素”
*/
boolean hasWaitingConsumer();
/**
* 获取当前正在等待元素的消费者数目
*/
int getWaitingConsumerCount();
}
3. LinkedTransferQueue基于链表的传输队列
3.1 原理分析
基于链表实现传输队列。链表中的每个节点主要由以下属性组成:
// 用作标记是否是生产者占用的数据(true),反之,为消费者线程占用,为false
final boolean isData;
// 如果是生产者占用(isData=true),表示实际数据;如果是消费者占用(isData=false),表示null
// 通过CAS设置
volatile Object item;
// 下一个节点
volatile Node next;
// 等待的消费者线程,如果是生产者占用,为null
volatile Thread waiter;
同时,节点有两种状态(“未匹配”和“已匹配”),对应的节点属性变化可以由下面的表格表示:
匹配前 | 匹配后 | |
---|---|---|
生产者节点 | isData=true, item!=null | isData=true, item=null |
消费者节点 | isData=false, item=null | isData=false, item=this |
借助节点的类型,如果当前节点与队列中节点类型一致(同为生产/消费),则执行入队操作;如果节点类型不一致,则执行出队操作,更新队首位置。
附上参考资料:
3.2 源码
由于所有的队列操作都由xfer核心方法实现,这里我只贴xfer的代码分析
/*
* 核心xfer方法的how参数可能的取值
*/
// 用于非阻塞的poll, tryTransfer
private static final int NOW = 0;
// 用于offer、put、add插入操作(由于无界,所以都不会阻塞)
private static final int ASYNC = 1;
// 用于无限阻塞的transfer、take
private static final int SYNC = 2;
// 用于有限时间阻塞的poll、tryTransfer
private static final int TIMED = 3;
/**
* 核心方法,用于实现所有的队列方法
* @param e 传输的数据或null值
* @param haveData 是否插入类操作,如果true, 参数e不能为空
* @param how 表示是哪类操作:NOW/ASYNC/SYNC/TIMED
* @param nanos 超时时间(纳秒)
*/
private E xfer(E e, boolean haveData, int how, long nanos) {
// haveData 为true,表示是插入类操作,必须保证e不为空,否则抛NPE
if (haveData && (e == null))
throw new NullPointerException();
// 将要添加的节点
Node s = null;
retry:
for (;;) {
// restart on append race
// 现有节点遍历
for (Node h = head, p = h; p != null;) {
// find & match first node
boolean isData = p.isData;
Object item = p.item;
// 确保节点为未匹配状态
if (item != p && (item != null) == isData) {
// unmatched
// 节点与此次操作模式一致,无需执行匹配过程,跳出循环,进入添加节点操作
if (isData == haveData)
break;
// 匹配成功
if (p.casItem(item, e)) {
for (Node q = p; q != h;) {
Node n = q.next; // update by 2 unless singleton
// 更新head为匹配节点的next节点
if (head == h && casHead(h, n == null ? q : n)) {
// 将旧节点自连接
h.forgetNext();
break;
} // advance and retry
if (