基于JDK1.8详细介绍了LinkedTransferQueue的底层源码实现,包括入队、出队、传递等操作源码,以及相比于LinkedBlockingQueue和SynchronousQueue的改进之处!
文章目录
1 LinkedTransferQueue的概述
public class LinkedTransferQueue< E >
extends AbstractQueue< E >
implements TransferQueue< E >, Serializable
LinkedTransferQueue来自于JDK1.7的JUC包,是一个支持并发操作的无界阻塞队列,由于出现的比较晚,相比于之前的阻塞队列特别是LinkedBlockingQueue和SynchronousQueue有了很多改进的地方,性能也更加强大。
LinkedTransferQueue底层数据结构同样是由单链表组成的双重队列结构,这类似于SynchronousQueue的公平模式。
实现了TransferQueue接口,内部是一些transfer方法,某些情况下生产者能够直接传递数据给消费者而不必存入队列,其实相当于SynchronousQueue中的transfer方法的开放版本,这在后面的源码部分会详细讲解。
实现了Serializable接口,支持序列化;没有实现Cloneable接口,不支持克隆!
不支持null元素!
LinkedTransferQueue基于LinkedBlockingQueue和SynchronousQueue,同时又作出了部分改进,因此如果我们先弄懂了这两个阻塞队列的原理,那么LinkedTransferQueue的原理就很简单了!相关文章如下:JUC—LinkedBlockingQueue源码深度解析、JUC—三万字的SynchronousQueue源码深度解析。
2 LinkedTransferQueue的原理
2.1 主要属性
首先是一批常量,MP、FRONT_SPINS、CHAINED_SPINS等用于阻塞之前的自旋次数计算。SWEEP_THRESHOLD表示进行整体垃圾清理的阈值。
队列采用单链表的双重队列实现,head和tail作为队列的头、尾结点,结点类型为Node内部类,里面的属性和SynchronousQueue中的TransferQueue内部类是一样。一个队列保存了两种模式的结点,通过Node的isData属性确定:isData=true:表示是生产者结点;isData=false:表示是消费者结点。
/**
* CPU可用线程数量是否大于1,在多cpu或者多核处理器上为true
*/
private static final boolean MP =
Runtime.getRuntime().availableProcessors() > 1;
/**
* 当前结点作为队列中第一个等待结点的时候,结点在阻塞前的自旋次数
* 该值是经验推导的 ——它适用于各种处理器、CPU 数量和 OS。
*/
private static final int FRONT_SPINS = 1 << 7;
/**
* 当结点前面是另一个明显正在自旋的结点时,在该结点阻塞之前要自旋的次数
* FRONT_SPINS的两倍
*/
private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;
/**
* 可容忍的 尝试从队列移除被删除的结点的失败次数 的最大值,
* 当sweepVotes大于等于SWEEP_THRESHOLD时,将扫描全部队列并移除遇到的被删除的结点
*/
static final int SWEEP_THRESHOLD = 32;
/**
* 队列类的头结点引用,可以为null,head指向的结点不一定是队列真正的头结点
* 具有volatile的特性
*/
transient volatile Node head;
/**
* 队列类的尾结点引用,可以为null,tail指向的结点不一定是队列真正的尾结点
* 具有volatile的特性
*/
private transient volatile Node tail;
/**
* 尝试从队列移除被删除的结点的失败次数
* 具有volatile的特性
*/
private transient volatile int sweepVotes;
/**
* 结点内部类,内部属性和SynchronousQueue中的TransferQueue内部类是一样的
*/
static final class Node {
/**
* 是否存放了数据,即是否是request结点,true 是 false 否
* final修饰,初始化之后不可改变
*/
final boolean isData; // false if this is a request node
/**
* 数据域
* 具有volatile的特性
*/
volatile Object item; // initially non-null if isData; CASed to match
/**
* 后继
* 具有volatile的特性
*/
volatile Node next;
/**
* 当前结点所属的线程,用于控制 park/unpark
* 具有volatile的特性
*/
volatile Thread waiter; // null until waiting
/**
* Node的构造器
*
* @param item 数据
* @param isData 是否存放数据
*/
Node(Object item, boolean isData) {
//使用unsafe的putObject方法,基于偏移量快速的赋值
UNSAFE.putObject(this, itemOffset, item); // relaxed write
//该值初始化之后不会被改变
this.isData = isData;
}
//省略相关方法,后面会讲
private static final long serialVersionUID = -3375979862319811754L;
//CAS操作都是使用UNSAFE来完成的,下面是UNSAFE相关初始化操作
private static final sun.misc.Unsafe UNSAFE;
private static final long itemOffset;
private static final long nextOffset;
private static final long waiterOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
itemOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
waiterOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiter"));
} catch (Exception e) {
throw new Error(e);
}
}
}
//省略相关方法,后面会讲
//CAS操作都是使用UNSAFE来完成的,下面是UNSAFE相关初始化操作
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;
private static final long sweepVotesOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = LinkedTransferQueue.class;
headOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("head"));
tailOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("tail"));
sweepVotesOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("sweepVotes"));
} catch (Exception e) {
throw new Error(e);
}
}
TransferQueue接口继承了BlockingQueue接口,具有自己的transfer系列方法,用于生产者(入队列)线程在一定条件下直接将元素传递给等待的消费者(出队列)线程,提升效率。下面是TransferQueue的介绍,我们会在LinkedTransferQueue中讲解这些方法的具体实现。
public interface TransferQueue<E> extends BlockingQueue<E> {
/**
* 如果存在消费者已经在等待接收元素(在BlockingQueue.take()或定时的poll ),则立即传递指定的元素并返回,否则元素被丢弃并返回
* 如果传递失败元素会被丢弃,该线程也不会阻塞。
*
* @param e 指定要传递的元素
* @return 如果元素已传递,返回true,否则返回false
* @throws ClassCastException 如果指定元素的类阻止它添加到此队列
* @throws NullPointerException 如果指定的元素为空
* @throws IllegalArgumentException 如果指定元素的某些属性阻止将其添加到此队列中
*/
boolean tryTransfer(E e);
/**
* 如果存在消费者已经在等待接收元素(在BlockingQueue.take()或定时的poll ),则立即传递指定的元素并返回,否则阻塞直到元素被要给消费者接收
*
* @param e 指定要传递的元素
* @throws InterruptedException 如果在等待时中断,在这种情况下元素会被丢弃
* @throws ClassCastException 如果指定元素的类阻止将其添加到此队列中
* @throws NullPointerException 如果指定的元素为空
* @throws IllegalArgumentException 如果指定元素的某些属性阻止将其添加到此队列中
*/
void transfer(E e) throws InterruptedException;
/**
* 如果存在消费者已经在等待接收元素(在BlockingQueue.take()或定时的poll ),则立即传递指定的元素并返回true
* 否则在指定时间范围内阻塞该线程并继续等待消费者接收元素(在BlockingQueue.take()或定时的poll ),传递成功则返回true
* 否则返回false,元素被丢弃
*
* @param e 指定要传递的元素
* @param timeout 等待时间
* @param unit 时间单位
* @return 传递成功返回true,否则返回false,元素被丢弃
* @throws InterruptedException 如果在等待时中断,在这种情况下元素会被丢弃
* @throws ClassCastException 如果指定元素的类阻止将其添加到此队列中
* @throws NullPointerException 如果指定的元素为空
* @throws IllegalArgumentException 如果指定元素的某些属性阻止将其添加到此队列中
*/
boolean tryTransfer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
/**
* @return 如果此时存在至少一个消费者在等待接收元素(在BlockingQueue.take()或定时的poll ),则立即返回true,这只是瞬时的状态。
*/
boolean hasWaitingConsumer();
/**
* @return 返回此时通过BlockingQueue.take()或定时器poll等待接收元素的消费者数量的估计。
*/
int getWaitingConsumerCount();
}
2.2 构造器
LinkedTransferQueue的构造器很简单。
2.2.1 LinkedTransferQueue()
public LinkedTransferQueue()
创建一个空的 LinkedTransferQueue。
/**
* 创建一个空的 LinkedTransferQueue。
*/
public LinkedTransferQueue() {
//什么也不做
}
2.2.2 LinkedTransferQueue( c )
public LinkedTransferQueue(Collection<? extends E> c)
创建一个 LinkedTransferQueue,包含指定集合的远不元素,以指定集合的迭代器的遍历顺序添加。
如果指定的集合或其任何元素为null,则抛出NullPointerException。
/**
* 创建一个 LinkedTransferQueue,包含指定集合的远不元素,以指定集合的迭代器的遍历顺序添加。
*
* @param c 指定集合
* @throws NullPointerException 如果指定的集合或其任何元素为null
*/
public LinkedTransferQueue(Collection<? extends E> c) {
this();
//直接调用了AbstractQueue的公共addAll方法
addAll(c);
}
/**
* AbstractQueue的公共addAll方法
* 将指定集合中的所有元素都添加到此集合中。
*
* @param c 包含要添加到此集合的元素的指定集合
* @return 如果此集合由于调用而发生更改,则返回 true
* @throws ClassCastException 如果指定集合中某个元素的类不允许它添加到此集合中
* @throws NullPointerException 如果指定集合包含 null 元素,并且此集合不支持 null 元素,或者指定集合为 null
* @throws IllegalArgumentException 如果指定集合的元素的某属性不允许它添加到此集合中
* @throws IllegalStateException 如果由于插入限制,不是所有的元素都能在此时间添加
*/
public boolean addAll(Collection<? extends E> c) {
//如果c为null,抛出NullPointerException
if (c == null)
throw new NullPointerException();
//如果自己添加自己,抛出IllegalArgumentException
if (c == this)
throw new IllegalArgumentException();
//此集合结构改变的标志位,false表示此集合结构没有更改,true表示已更改
boolean modified = false;
/*循环指定集合,调用add方法*/
for (E e : c)
if (add(e))
//add添加成功,那么modified置为true
modified = true;
//返回modified
return modified;
}
2.3 xfer核心方法
LinkedTransferQueue的出队、入队、传递元素等外部api方法都依赖一个内部的xfer方法的实现。使用同一个方法可以减少方法数量,但是会增加单个方法的复杂度,xfer方法代码比较多,也比较难以理解。xfer方法类似SynchronousQueue的transfer方法。
xfer方法有四个参数:
- e:对于入队、传递方法,e为指定元素,不能为null;对于出队方法,e为null。
- haveData:对于入队、传递方法,haveData为true;对于出队方法,haveData为false。
- how:四种方法模式,NOW, ASYNC, SYNC, or TIMED。
- nanos:超时时间纳秒,仅在TIMED模式中使用。
不同的方法模式有不同的含义:
/*
* LinkedTransferQueue的外部api方法都依赖一个内部的xfer方法的实现
* 对于不同的api方法,传递给xfer方法的“how”参数不同,通过不同的参数xfer方法走不同的逻辑
*/
/**
* 不会阻塞的其他方法,出队 - poll()、remove()、尝试传递 - tryTransfer(e)
*/
private static final int NOW = 0; // for untimed poll, tryTransfer
/**
* 不会阻塞的入队方法,入队 - offer(e)、put(e)、offer(e, timeout, unit)、add(e)
*/
private static final int ASYNC = 1; // for offer, put, add
/**
1. 非超时的阻塞的方法,传递 - transfer()、出队 - take()
*/
private static final int SYNC = 2; // for transfer, take
/**
2. 超时的阻塞的方法,超时传递 - tryTransfer(e, timeout, unit)()、超时出队 - poll(timeout, unit)
*/
private static final int TIMED = 3; // for timed poll, tryTransfer
xfer的大概步骤有两步:
- 在循环中遍历队列,主动查找并且尝试匹配对应的结点,匹配成功之后即可返回,这一步中,不同的方法根据isData属性去识别可能匹配的结点,只有不同的isData请求才能匹配,即只有入队、传递方法和出队方法匹配;
- 如果没有找到能够匹配的结点,不同的方法根据自己传入的how参数走不同的逻辑,可能会直接返回(NOW),或者添加结点再返回(ASYNC),或者等待被后来的请求匹配(AYNC、TIMED),直到被匹配成功或者取消等待或者超时,随后返回!
上面的大概步骤比较笼统,而且很多的细节都没有讲到,详细步骤为:
- 参数校验,如果是入队、传递方法,但是e为null,那么抛出NullPointerException。
- 初始化Node变量s,作为要追加到队列中的结点(可能会用到),定义在循环之外可被复用。随后开启一个最外层的大死循环,完成不同方法自己的逻辑:
- 内部再开启第一层循环,用于尝试查找并匹配结点,这一步对于所有的方法都是一样代码。初始化条件:h保存最新的head,p保存h,从队头开始;判断条件:如果p不为null,那么继续循环:
- isData保存p的isData属性,item保存p的item属性。
- 如果通过p的item校验p没有被取消和匹配,当前请求就可能和p结点进行匹配:
- 如果isData和当前请求的haveData属性一致,那就说明是同一个类型的请求,不能匹配,因为只有入队、传递操作和出队操作能够匹配。随后break跳出第一层内循环,继续下面的步骤;这里可以直接跳出循环的原因就是新来的请求都是从队头到队尾的顺序点匹配的,如果前面有同一个类型的请求的结点没有被匹配,那么后来的同一个类型的请求肯定也不会匹配成功,该请求构造的结点会加入队列尾部。
- 到这一步,说明可以尝试匹配:在当前请求中使用p调用casItem方法,尝试CAS的将p的item属性的值从item设置为e。如果CAS成功,那么表示匹配成功,进行下面的后续步骤:
- 内部再开启第二层循环,尝试查找并移除被匹配或者取消的结点,以及更新head引用的指向。初始化条件:q保存刚刚匹配的p,判断条件:如果q不等于h,即如果p不是第一个循环的结点,那么继续循环:
- 获取q的后继n。如果此时h还是指向head头结点,即还没有被其他线程头结点,那么尝试cas的将head从h指向q(n为null)或者n(n不为null)。如果都为true,那么表示head指向更新成功,原h结点可以出队。h的后继指向自己,辅助h出队列,实际上最新head指向的结点的前驱结点在之后都会因为没有外部引用而被GC回收,只有原head结点出队的时候结点的next才会指向知己。break跳出第二层内循环,继续下面的语句,将会返回。
- 到这里,说明此时head就已经被其他线程改变了,或者CAS在竞争改变head引用指向时失败了,即移除已匹配的p结点失败。将h指向此时的最新的head,如果:h为null,或者 h不为null,q指向h的后继,如果q为null,或者 h、q都不为null,如果q没有被匹配或者取消,即如果结点数量小于2,或者被匹配和取消的结点数量小于2,那么break跳出第二层内循环,继续下面的语句,将会返回。
- 匹配成功之后,unpark唤醒p结点中的阻塞等待的线程,可能存在也可能不存在,都没关系。
- 匹配成功之后,返回匹配的结点p原来的item的值,xfer方法结束。这是所有的入队、传递、出队方法在主动匹配成功之后的共同唯一出口。
- 内部再开启第二层循环,尝试查找并移除被匹配或者取消的结点,以及更新head引用的指向。初始化条件:q保存刚刚匹配的p,判断条件:如果q不等于h,即如果p不是第一个循环的结点,那么继续循环:
- 如果通过p的item校验p已被被取消和匹配,那么获取p的后继n,向后继续匹配。
- 判断p是否不等于n,即p是否没有出队。如果是,那么p设置为n,继续下一轮第一层循环,相当于继续匹配后继;如果不是,那么h设置为此时最新的的head,p设置为h,继续下一轮第一层循环,相当于重新开始第一层循环。
- 到这一步,肯定出跳出了上面的循环并且没有返回。可能是队列为空,或者已经遍历了整个队列还是匹配失败了,走失败的逻辑;这里才会使用到“how”属性,不同的方法根据不同的how属性走不同的逻辑,而上面的尝试匹配和匹配成功的逻辑都是一样的。
- 如果how不等于 NOW,即表示:如果是NOW的模式,即不会阻塞的其他方法,出队 - poll()、remove()、尝试传递 - tryTransfer(e),这两个方法就算匹配失败也直接返回,不会进入if代码块。如果是ASYNC、SYNC、TIMED的模式,那么进入if代码块:
- if代码块中,首先判断要追加到队列中的结点s如果为null,那么新建Node结点,item属性为e,isData属性为haveData,即item为入队、传递的元素。
- 调用tryAppend尝试追加s结点到队列尾部,返回值为pred,追加成功则返回s的前驱,没有前驱就返回s自己;追加失败就返回null。
- 如果pred为null,表示追加失败,因为可能是在此过程中新入队了可以匹配的结点(xfer方法没有加锁),那么continue retry结束本次最外层的大循环,相当于重头再来走一次,包括匹配的步骤。
- 到这里,表示追加结点成功,继续判断如果how不等于 ASYNC,即表示:如果是ASYNC的模式,即不会阻塞的入队方法,offer(e)、put(e)、offer(e, timeout, unit)、add(e),它们将数据构造为结点加入了队列,但是它们的请求本身(线程)也可以返回了,将会出if代码块执行最后的返回;而如果是SYNC、TIMED的模式的请求,表示等待和超时等待的方法,它们除了将数据构造为结点加入了队列,还会进入内层的if代码块:
- 调用awaitMatch方法,在awaitMatch方法中等待直到被后续的请求匹配或者被取消,这是需要等待的一系列方法在主动匹配失败之后进行等待被动匹配并返回的统一出口。如果后续被匹配成功,awaitMatch返回匹配的请求的item值;如果后续因为中断或者超时被取消等待,awaitMatch返回该请求自己的e(item)值,xfer方法结束。
- 返回e的值,xfer方法结束。这是不需要等待的一系列方法,主动匹配失败之后的统一出口。这里返回的就是该请求自己的e(item)值。
- 内部再开启第一层循环,用于尝试查找并匹配结点,这一步对于所有的方法都是一样代码。初始化条件:h保存最新的head,p保存h,从队头开始;判断条件:如果p不为null,那么继续循环:
这里还有一些细节:
- 只有入队、传递方法可以和出队的方法互相匹配。整个匹配过程会尝试重头到尾的循环遍历整个队列,因此同时允许多个请求的并发匹配操作。某个请求在匹配过程中如果发现了同一个请求类型(isData)的结点没有被匹配也没有被取消,那么该请求直接算作匹配失败。
- 主动匹配的方法就是casItem方法,这个而方法是在当前请求中由等待匹配的结点p调用的,该方法会将p结点原来的item改为e。从这里可以看出来:如果当前请求是一个入队、传递请求,那么它匹配的p一定是出队请求,p的item将会被从null设置为要传递的数据e;如果当前请求是一个出队请求,那么它匹配的p一定是入队、传递请求,p的item将会被从传递的数据e设置为null。匹配成功之后返回被匹配结点p的原来的item,因此如果该请求是入队、传递方法,那么应该返回null,如果是出队方法,则返回的值就代表出队的元素。
- 不会阻塞的其他方法(NOW),在匹配失败之后直接返回;不会阻塞的入队方法(ASYNC),在匹配失败之后,会将入队的元素包装成一个结点加入队列,随后同样直接返回;等待和超时等待的方法(SYNC、TIMED),在匹配失败之后,出了构建结点加入队列之外,还会调用awaitMatch继续等待被匹配,直到被匹配或者被取消返回返回。
- 匹配成功之后,被匹配的结点不一定会出队,head的指向不一定会改变。如果匹配的结点就是head结点,那么什么都不管,否则将会尝试出队操作,但是也不一定,如果发生了CAS修改head 的冲突,那么会判断如果结点数量小于2,或者被匹配和取消的结点数量小于2,同样也直接返回。这个2就是“松弛度”,实际上不必每次匹配成功都要求CAS的更新head的指向,这样减少了线程竞争,提升并发效率,而如果松弛度大于等于2,则可能会增加查找没有被匹配的结点的时间,这个松弛度的取值是一个“经验值",详细解释见源码。
/**
1. 所有的出队、入队、传递元素的方法的底层公用方法实现
2. 3. @param e 对于入队、传递方法,e为指定元素,不能为null;对于出队方法,e为null
4. @param haveData 对于入队、传递方法,haveData为true;对于出队方法,haveData为false
5. @param how 四种方法模式:NOW, ASYNC, SYNC, or TIMED
6. @param nanos 超时时间纳秒,仅在TIMED模式中使用
7. @return 该请求匹配的结点(请求)的item值,或者其他异常情况值
8. @throws NullPointerException 如果是入队、传递方法,但是e为null
*/
private E xfer(E e, boolean haveData, int how, long nanos) {
//如果是入队、传递方法,但是e为null,那么抛出NullPointerException
if (haveData && (e == null))
throw new NullPointerException();
//要追加到队列中的结点(可能会用到),可被复用
Node s = null; // the node to append, if needed
/*开启一个死循环,完成不同方法自己的逻辑*/
retry:
for (; ; ) { // restart on append race
/*
* 1 内部再开启第一层循环,用于尝试查找并匹配结点,这一步对于所有的方法都是一样代码
*
* 初始化条件:h保存最新的head,p保存h,从队头开始
* 判断条件:如果p不为null,那么继续循环
*/
for (Node h = head, p = h; p != null; ) { // find & match first node
//isData保存p的isData属性
boolean isData = p.isData;
//item保存p的item属性
Object item = p.item;
/*
* 1 如果item不等于p,表示p没有被取消,也没有被匹配(或者还没有匹配完毕)
* 2 获取item是否等于null的判断结果,如果结果等于isData,说明p还没有被匹配。
* 因为一般情况下item为null那么isData为false,item不为null那么isData为true
*
* 上面两个条件都满足,说明那么p没有被取消和匹配,当前请求就可能和p结点进行匹配
* 这类似于SynchronousQueue的公平模式的匹配机制
*/
if (item != p && (item != null) == isData) { // unmatched
/*如果isData和当前请求的haveData属性一致,那就不能匹配,只有入队、传递操作和出队操作能够匹配*/
if (isData == haveData) // can't match
/*
* break跳出第一层内循环,继续下面的步骤
* 这里可以直接跳出循环的原因就是新来的请求都是从队头到队尾的顺序点匹配的,如果前面有同一个类型的请求的结点没有被匹配
* 那么后来的同一个类型的请求肯定也不会匹配成功,该请求构造的结点会加入队列尾部。
*/
break;
/*
* 到这一步,说明可以尝试匹配,到这里也没有加锁,因此可能匹配失败,这里的匹配和SynchronousQueue的公平模式的匹配很相似
* 在当前请求中使用p调用casItem方法,尝试CAS的将p的item属性的值从item设置为e
* 如果CAS成功,那么表示匹配成功;
* 如果CAS失败,那么表示匹配失败,可能是被取消了,或者被其他请求抢先匹配了;
*
* 从这里可以看出来:
* 如果当前请求是一个入队、传递请求,那么它匹配的p一定是出队请求,p的item将会被从null设置为要传递的数据e;
* 如果当前请求是一个出队请求,那么它匹配的p一定是入队、传递请求,p的item将会被从传递的数据e设置为null。
*/
if (p.casItem(item, e)) { // match
/*
* 匹配成功之后,内部再开启第二层循环,尝试查找并移除被匹配或者取消的结点,以及更新head指向
* 初始化条件:q保存刚刚匹配的p;
* 判断条件:如果q不等于h,即如果p不是第一个循环的结点,那么继续循环
*
* 在第一层循环中,p最开始就是指向头结点的,如果第一次循环p就被匹配成功了,那么q = p也是指向头结点的
* 那么此时q等于h,那么就不会尝试移除被匹配的结点,而会直接返回,这里被匹配的结点将会留待后续请求再移除
*/
for (Node q = p; q != h; ) {
/*
* 第一次进入这里面,表示该请求一定是匹配成功了,并且匹配的结点也不是最开始的head结点—h,即h是已经被匹配了或者取消了的,此时一并移除
* 但是head是可能一直在变化的,此时的h也可能不再是真正的head结点了,即被其他线程改变了
*/
//获取q的后继n
Node n = q.next; // update by 2 unless singleton
/*
* 如果此时h还是指向head头结点,即还没有被其他线程头结点,那么尝试cas的将head从h指向q(n为null)或者n(n不为null)
* 如果两边都为true,那么表示head指向更新成功,原h结点可以出队
*/
if (head == h && casHead(h, n == null ? q : n)) {
//h的后继指向自己,h出队列
//实际上最新head指向的结点的前驱结点在之后都会因为没有外部引用而被GC回收,只有原head结点出队的时候结点的next才会指向知己
h.forgetNext();
//break跳出第二层内循环,继续下面的语句,将会返回
break;
} // advance and retry
/*
* 到这里,说明此时head就已经被其他线程改变了,或者CAS在竞争改变head时失败了,即移除已匹配的p结点失败
* 有可能此时h被其他请求移除了,有可能此时h和p都被其他请求帮助移除了
*/
/*
* 将h指向此时的最新的head,如果:
* h为null
* 或者h不为null,q指向h的后继,如果q为null
* 或者h、q都不为null,如果q没有被匹配或者取消
*/
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
/*
* 即如果结点数量小于2,或者被匹配和取消的结点数量小于2,那么break跳出第二层内循环,继续下面的语句,将会返回
*
* 如果不满足上面的条件,此时h赋值为最新head,p为h的后继,进入下一次循环
* 在下一次的循环中的第一个if,head将会被尝试CAS的设置为p的后继n(如果n为null)或者p(如果n不为null)
*
* 某队列结构:h -> n -> nn -> nnn
* 如果请求A匹配头结点h成功,同时请求B匹配h的后继n成功,同时请求C匹配n的后继nn成功,同时请求D匹配nn的后继nnn成功
* 那么请求A由于匹配的head因此不会进入for循环,这个h留待后续结点清理
* 此时请求B、C、D都会首先尝试CAS的设置head,并移除已被匹配的结点,如果它们都走到for循环的第一个if语句,明显只能由一个线程可以成功
* 假设B成功了,那么此时head指向nn,随后退出循环,队列结构 nn -> nnn;请求C、D都失败了,那么它们都走到第二个if
* 此时队列有超过两个结点被匹配和取消,还不能返回,C、D继续下一次循环,同样在第一个f只有一个线程能够成功,
* 假设D成功了,那么此时head指向nnn,随后退出循环,队列结构为:nnn,
*
* 线程继续C走到第二个if,此时队列没有超过两个结点被匹配和取消,因此请求C也退出循环,队列结构为:nnn,head也指向该结点,即还剩一个被匹配的结点
* 如果nnn后面还有 没有被匹配的结点,那么此时head指向没有被匹配的结点
*
* 实际上LinkedTransferQueue最多允许一个被删除或者取消的结点存在,这里的“unless slack”翻译成中文就是松弛度的意思,
* LinkedTransferQueue的松弛度被设置为1,这样不必每次匹配成功都CAS的更新head的指向,减少了线程竞争,提升并发效率,
* 而如果松弛度大于等于2,则可能会增加查找没有被匹配的结点的时间,这个松弛度的取值是一个“经验值"
*/
break; // unless slack < 2
}
//入队、传递、出队的方法的匹配逻辑都是一样的,匹配成功之后都会走到这一步
//匹配成功之后,unpark唤醒p结点中的阻塞等待的线程
LockSupport.unpark(p.waiter);
//匹配成功之后,返回p原来的item的值,xfer方法结束,如果该请求是入队、传递方法,那么应该放那会null,如果是出队方法,则返回出队的元素
return LinkedTransferQueue.<E>cast(item);
}
}
/*
* 到这里,说明本次匹配失败
* 可能是p被取消了,或者p被其他请求抢先匹配了
*/
//获取p的后继n,向后继续匹配
Node n = p.next;
/*
* 判断p是否不等于n,即p是否没有出队
* 如果是,那么p设置为n,继续下一轮第一层循环,相当于继续匹配后继
* 如果不是,那么h设置为此时的head,p设置为h,继续下一轮第一层循环,相当于重新开始第一层循环
*
* 这里说明,xfer方法同时允许多个请求匹配多个结点,但是如果遇到某个结点出队列了,那么重新获取head,重新开始
*/
p = (p != n) ? n : (h = head); // Use head if p offlist
}
/*
* 2 到这一步,肯定出跳出了上面的循环并且没有返回,可能是队列为空,或者已经遍历了整个队列,表示匹配失败了,走失败的逻辑
* 这里才会使用到“how”属性,不同的方法根据不同的how属性走不同的逻辑,而上面的尝试匹配和匹配成功的逻辑都是一样的
*/
//如果how不等于 NOW,即表示:
//如果是ASYNC、SYNC、TIMED的模式,那么进入if代码块
//如果是NOW的模式,即不会阻塞的其他方法,出队 - poll()、remove()、尝试传递 - tryTransfer(e),这两个方法就算匹配失败也直接返回,不会进入if代码块
if (how != NOW) { // No matches available
//要追加到队列中的结点s如果为null,那么初始化
if (s == null)
//那么新建Node结点,item属性为e,isData属性为haveData,即item为入队、传递的元素
s = new Node(e, haveData);
//调用tryAppend尝试追加s结点到队列尾部,返回值为pred,追加成功则返回s的前驱,没有前驱就返回s自己;追加失败就返回null
Node pred = tryAppend(s, haveData);
//如果pred为null,表示追加失败,那么结束本次最外层的大循环,相当于重头再来走一次,包括匹配的步骤
if (pred == null)
continue retry; // lost race vs opposite mode
/*
* 到这里,表示追加结点成功,判断如果how不等于 ASYNC,即表示:
* 1 如果是SYNC、TIMED的模式,表示等待和超时等待的方法,那么进入if代码块
* 2 如果是ASYNC的模式,即不会阻塞的入队方法,入队 - offer(e)、put(e)、offer(e, timeout, unit)、add(e)
*
*
* 这些非阻塞的入队方法如果没有匹配成功,将同样会构造结点(保存元素值和isData属性)并加入到队尾,但是线程不会等待而是直接返回
* 这实际上就是linkedTransferQueue相比于SynchronousQueue的优点之一,即这个内部队列可以存储元素,并且不会阻塞线程(可选)
*/
if (how != ASYNC) {
/*
* 剩下的就是等待的方法了,调用awaitMatch方法阻塞等待直到被后续的请求匹配或者被取消
* 这是需要等待的一系列方法在主动匹配失败之后进行等待被动匹配并返回的统一出口
* 这里通过判断how是否等于TIMED模式来判断是否是超时等待模式
*
* 如果被匹配成功,返回匹配的请求的item值;如果因为中断或者超时被取消等待,返回当前结点的e(item)
*/
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
}
/*
* 这是不需要等待的方法,主动匹配失败之后的统一出口,这里返回的就是该请求自己的e(item)值。
*/
return e; // not waiting
}
}
2.3.1 CAS操作
LinkedTransferQueue中对于引用属性的赋值基本都是使用的CAS操作,这样能够保证单个变量符合操作的原子性,同时避免了锁的使用,提升了性能。
常用的有如下CAS方法:
- casHead,位于LinkedTransferQueue中的方法,尝试CAS的改变队头head属性的指向,从cmp指向val;
- casTail,位于LinkedTransferQueue中的方法,尝试CAS的改变队尾tail属性的指向,从cmp指向val;
- casSweepVotes,位于LinkedTransferQueue中的方法,尝试CAS的改变sweepVotes(整体清理计数)属性的值,从cmp变成val;
- casNext,位于Node中的方法,尝试CAS的改变结点next属性的指向,从cmp指向val;
- casItem,位于Node中的方法,尝试CAS的改变结点item属性的指向,从cmp指向val;
/**
* 位于LinkedTransferQueue中的方法
* 尝试CAS的将head的引用指向从cmp指向val
*
* @param cmp 预期head
* @param val 新head
* @return true 成功 false 失败
*/
private boolean casHead(Node cmp, Node val) {
return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
}
/**
* 位于LinkedTransferQueue中的方法
* 尝试CAS的将tail的引用指向从cmp指向val
*
* @param cmp 预期tail
* @param val 新tail
* @return true 成功 false 失败
*/
private boolean casTail(Node cmp, Node val) {
return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
}
/**
* 位于LinkedTransferQueue中的方法
* 尝试CAS的将sweepVotes属性的值从cmp改为val
*
* @param cmp 预期sweepVotes
* @param val 新sweepVotes
* @return true 成功 false 失败
*/
private boolean casSweepVotes(int cmp, int val) {
return UNSAFE.compareAndSwapInt(this, sweepVotesOffset, cmp, val);
}
/**
* 位于Node中的方法
* 尝试CAS的将调用结点的next从cmp指向val
*
* @param cmp 预期next
* @param val 新next
* @return true 成功 false 失败
*/
final boolean casNext(Node cmp, Node val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
/**
* 位于Node中的方法
* 尝试CAS的将调用结点的item从cmp指向val
*
* @param cmp 预期item
* @param val 新item
* @return true 成功 false 失败
*/
final boolean casItem(Object cmp, Object val) {
// assert cmp == null || cmp.getClass() != Node.class;
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
2.3.2 forget操作
两个forget方法,基于unsafe的putObject方法基于偏移量为属性快速的赋值。他们都是在某个条件完成之后才会调用,后续不会再次使用到这些属性,因此使用普通快速写入。
/**
* 位于Node中的方法
* 将调用结点的next属性指向自己,即从队列中移除该结点
* 仅仅在调用结点是原head结点并且更新head指向成功的之后才会调用,因此使用普通快速写入
*/
final void forgetNext() {
UNSAFE.putObject(this, nextOffset, this);
}
/**
* 位于Node中的方法
* 将调用结点的item设置为结点自己,将waiter置为null
* 仅仅在某个结点被匹配或者被取消之后调用,因此使用普通快速写入
*/
final void forgetContents() {
UNSAFE.putObject(this, itemOffset, this);
UNSAFE.putObject(this, waiterOffset, null);
}
2.3.3 isMatched检验匹配/取消
isMatched是位于Node中的方法,如果调用结点被匹配或者被取消,那么返回true,否则返回false。
如果某个结点的item值指向结点自己,表示被取消或者匹配成功(执行了forgetContents方法);
或者item值不指向结点自己,继续获取item是否等于null的结果,如果该结果等于该结点的isData属性,表示被匹配了(还没执行forgetContents方法),因为如果isData为true(入队、传递),并且结点没有被匹配(还没执行forgetContents方法),那么x(item)一定不为null;如果isData为false(出队),并且结点没有被匹配(还没执行forgetContents方法),那么x(item)一定为null。
以上两种情况,满足一种即返回true!
/**
1. 位于Node中的方法
2. 如果调用结点被匹配或者被取消,那么返回true
*/
final boolean isMatched() {
//x保存此时调用结点的item值
Object x = item;
/*
* 如果x等于自身,表示被取消或者匹配成功(执行了forgetContents方法);
* 或者 x不等于自身,获取x是否等于null的结果,如果该结果等于isData,表示被匹配了(还没执行forgetContents方法),因为:
* 如果isData为true(入队、传递),并且结点没有被匹配和取消,那么x(item)一定不为null;
* 如果isData为false(出队),并且结点没有被匹配和取消,那么x(item)一定为null
*
* 以上两种情况,满足一种即返回true
*/
return (x == this) || ((x == null) == isData);
}
2.3.4 tryAppend尝试追加结点
tryAppend是位于LinkedTransferQueue中的方法,尝试将结点尝试作为追加到队列尾部,并更新tail引用的指向。在某个ASYNC、SYNC、TIMED模式的请求中,如果没有匹配成功,那么会构建结点并加入队列尾部,就是调用的tryAppend方法。
tryAppend方法实际上就是遍历队列,要么重tail开始,要么重head开始这取决于tail指向的结点是否存在或者在遍历过程中被出队。为什么要作者么复杂的工作呢?因为xfer方法实际上都没有加锁,允许多线程并发,在xfer的匹配失败到tryAppend方法之间,可能有其他线程更新了队列结构,此时的队列结构存在新加入的可以匹配的结点情况是完全存在的,因此需要进一步检查队列结点,如果发现可以匹配,那么tryAppend返回null;如果不能匹配,那么追加到队列尾部,并有可能更新tail的指向(也可能不更新这类似于head的更新逻辑,具体看代码注释),那么tryAppend返回追加的结点在队列中的前驱。
大概步骤为:
- 开启一个死循环,尝试将新结点追加到队列尾部,并更新tail引用的指向。初始化条件:t保存最新的tail,p保存t:
- n保存临时后继,u保存临时tail。如果p为null,表示尾结点引用为null或者p被移出队列了,那么p设置为head,从头开始查找整个队列看是否有匹配的结点,并且,如果p还是为null,那么表示队列为空,进入if代码块:
- 尝试cas的将head从null指向s,返回新加入的s,方法结束。这里就是初始化队列的操作,注意并没有初始化尾结点,tail还是指向null。
- 否则,队列不为空。调用p.cannotPrecede方法,判断当前请求追加的结点是否不能追加到调用结点p后面,如果p结点和当前请求可以匹配,那么将返回true,表示不能追加。如果不能追加,那么返回null,tryAppend方法结束。
- 否则,可以追加。此时获取p的后继n,如果n不为null,说明此时又新追加了结点到队列,或者tail不是只想真正的队尾,或者还没有遍历完整个队列(重头开始的情况),需要向后推进:
- 如果p不等于t(最开始只有一个结点或者循环了超过一次),u设置为tail,并且t不等于u,表明最新tail有变,那么t设置为u,同时p设置为t,相当于重新重tail开始循环;
- 否则,如果p不等于n,即p没有出队列,那么p设置为n,向后推进继续下一次循环;否则表示p被移除了队列,p设置为null,相当于重新重head开始循环。
- 到这里,p的后继为null,作为最后一个结点,开始追加,尝试CAS的将p的后继从null设置为s,返回true表示成功,返回false表示失败。如果CAS失败,那么p设置为p的后继,继续下一次循环;
- 如果CAS成功,那么尝试改变tail的指向。如果p不等于t,表示循环了不止一次,此时tail可能并没有指向真正的尾结点,那么尝试更新tail。这里就像上面的匹配成功之后改变head引用指向的情况,并不是每一次添加了结点都需要改变tail的引用指向,如果添加的结点就位于tail之后,那么就不需要改变tail的指向,详细解释见代码注释。最终无论有没有更新tail,都会返回p,p就是追加的s在队列中的“前驱”。
- n保存临时后继,u保存临时tail。如果p为null,表示尾结点引用为null或者p被移出队列了,那么p设置为head,从头开始查找整个队列看是否有匹配的结点,并且,如果p还是为null,那么表示队列为空,进入if代码块:
/**
* 位于LinkedTransferQueue中的方法
* 尝试将结点追加到队列尾部,并更新tail引用的指向
*
* @param s 需要被追加的结点
* @param haveData 对于入队、传递方法,haveData为true;对于出队方法,haveData为false
* @return 尝试追加s结点到队列尾部,追加成功则返回s的前驱,没有前驱就返回s自己;追加失败就返回null
*/
private Node tryAppend(Node s, boolean haveData) {
/*
* 开启一个死循环,尝试将新结点追加到队列尾部,并更新tail引用的指向
* 初始化条件:t保存最新的tail,p保存t
*/
for (Node t = tail, p = t; ; ) { // move p to last node and append
//n保存临时后继,u保存临时tail
Node n, u; // temps for reads of next & tail
//如果p为null,即尾结点引用为null,或者p被移出队列了,那么p设置为head,从头开始查找整个队列看是否有匹配的结点
//并且 如果p还是为null,那么表示队列为空,进入if代码块
if (p == null && (p = head) == null) {
//尝试cas的将head从null指向s
if (casHead(null, s))
//返回新加入的s,方法结束,这里就是初始化队列的操作,注意并没有初始化尾结点,tail还是指向null
return s; // initialize
}
/*
* 到这里,表示p不为null,这个p也可能是新添加的结点
* 这里需要调用p.cannotPrecede方法,判断当前请求追加的结点是否不能追加到调用结点p后面
* 如果p结点和当前请求可以匹配,那么将返回true,表示不能追加
*/
else if (p.cannotPrecede(haveData))
//如果不能追加,那么返回null
return null; // lost race vs opposite mode
/*
* 获取p的后继n,如果n不为null,说明此时又新追加了结点到队列,或者tail不是只想真正的队尾,或者还没有遍历完整个队列(重头开始的情况),需要向后推进
*/
else if ((n = p.next) != null) // not last; keep traversing
{
/*
* 如果p不等于t(最开始队列只有一个结点或者循环了超过一次),u设置为tail,并且t不等于u,表明最新tail有变,那么t设置为u,同时p设置为t,相当于重新重tail开始循环
* 否则,如果p不等于n,即p没有出队列,那么p设置为n,向后推进继续下一次循环;否则表示p被移除了队列,p设置为null,相当于重新重head开始循环
*/
p = p != t && t != (u = tail) ? (t = u) : // stale tail
(p != n) ? n : null; // restart if off list
}
/*
* 到这里,p的后继为null,作为最后一个结点,开始追加,尝试CAS的将p的后继从null设置为s
* 返回true表示成功,返回false表示失败
*/
else if (!p.casNext(null, s)) {
//如果CAS失败,那么p设置为p的后继,继续下一次循环
p = p.next; // re-read on CAS failure
}
/*如果CAS成功,那么尝试改变tail的指向*/
else {
/*
* 如果p不等于t,表示循环了不止一次,此时tail可能并没有指向真正的尾结点,那么尝试更新tail
* 这里就像上面的匹配成功之后改变head引用指向的情况,并不是每一次添加了结点都需要改变tail的引用指向,
* 如果添加的结点就位于tail之后,那么就不需要改变tail的指向
*
* 根据head和tail的特性:head可能不是真正的队列头,tail可能不是真正的不是队尾,据此我们可以想象到一下情况:
* head指向一个结点,tail指向null;head和tail都指向同一个结点;head和tail指向不同的结点;tail指向的结点甚至是head指向的结点的前驱…………还有很多
*/
if (p != t) { // update if slack now >= 2
//如果tail此时不等于t,或者tail等于t,但是尝试CAS的将tail指向s失败
//那么 t指向tail,如果t不为null
//那么 s指向t的后继,如果s不为null
//那么 s指向s的后继,如果s不为null
//那么 如果s不等于t
//以上情况都满足,将会一直循环,直到:本线程CAS改变tail成功,或者tail后面的结点小于等于两个,或者tail被移除队列了
while ((tail != t || !casTail(t, s)) &&
(t = tail) != null &&
(s = t.next) != null && // advance and retry
(s = s.next) != null && s != t) ;
}
//返回前驱p
return p;
}
}
}
2.3.4.1 cannotPrecede是否不可追加
cannotPrecede是位于Node中的方法,判断当前请求追加的结点是否不能追加到调用结点p后面。
很简单,类似于isMatched方法的逻辑:获取调用结点的isData属性d;如果d不等于haveData(当前请求的isData),表示当前请求追加的结点可以和调用结点p匹配;并且调用结点的item值x不等于调用结点自己,表示调用结点p没有被取消或者匹配成功(,执行了forgetContents方法);并且,获取x是否不等于null的结果,如果该结果等于d,表示调用结点p没有被匹配(没有执行casItem方法)。以上三个情况都满足,表明当前请求追加的结点可以和调用结点p匹配,即返回true,否则返回false。
/**
1. 位于Node中的方法
2. 判断当前请求追加的结点是否不能追加到调用结点p后面
3. 4. @param haveData 当前请求追加的结点的isData
5. @return true 不能追加 false 可以追加
*/
final boolean cannotPrecede(boolean haveData) {
//获取调用结点的isData属性d
boolean d = isData;
Object x;
/*
* 如果d不等于haveData,表示当前请求追加的结点可以和调用结点p匹配
* 并且 调用结点的item值x不等于调用结点自己,表示调用结点p没有取消
* 并且 获取x是否不等于null的结果,如果该结果等于d,表示调用结点p没有被匹配
*
* 以上情况都满足,表明当前请求追加的结点可以和调用结点p匹配
* 这个p结点一定是在第一步尝试查找并匹配结点之后新添加进来的,相当于错过了,
* 即本来在队列中没找到,然后刚出了循环,到准备追加结点之间,又新来了结点,此时就不能追加,应该重试
*/
return d != haveData && (x = item) != this && (x != null) == d;
}
2.3.5 awaitMatch等待被匹配/取消
awaitMatch是位于LinkedTransferQueue中的方法,只有SYNC和TIMED模式的方法在匹配失败之后才会调用,awaitMatch用于该请求以及对应结点等待被匹配或者取消。
有些类似于SynchronousQueue的公平模式的awaitFulfill,都是在一次循环中等待被匹配,最开始都是一系列是否可以返回的检查操作,但是还有明显的区别,awaitMatch的等待过程可能是自旋 <–> 随机让权(yield) -> park阻塞,即多了一个随机Thread.yield()的步骤,由于这里支持多线程并发,这样的操作有利于主动请求的线程优先执行,同样是提升在自旋的时候被匹配的效率。另外awaitMatch将因为被匹配返回和被中断返回的情况分开了,这也是值得注意的地方。
大概步骤为:
- 如果是超时操作,计算等待超时的时间点纳秒deadline,否则deadline初始化为0。
- 获取当前请求的线程w,自旋次数spins初始化为-1,伪随机数生成器randomYields,用于随机让权的判断;
- 开启一个死循环,等待被匹配或者取消:
- 首先是两个检查是否可以返回的if语句。获取s结点的此时的item值item,如果item不等于e(该请求原本的item),说明s结点被成功匹配了,此时已经在另一个请求中执行了casItem操作。
- 随后调用forgetContents,将s结点的item指向自己,将s的waiter指向null,清除多余的引用。可以看到被匹配的结点不会被对应的线程移除,实际上会在另一个主动匹配的新来的请求中被移除。
- item转换为E类型并返回,返回匹配的请求的原始item值。这是因为被取消而返回的出口。
- 如果w线程被中断了,或者超时等待时间到了还没有被匹配,那么表示结点可以取消等待了,那么s调用casItem尝试CAS的将调用结点的item从e指向s自己,表示被取消。如果在第一个if和第二个if之间s结点被成功匹配了,那么这个CAS将会失败,因为此时预期原值肯定不为e了,下一次循环将会在上一个if代码块中被返回。
- 随后调用unsplice尝试清理被取消的s结点,以及其他无效结点。
- 返回e,即返回当前请求的e。
- 上面的检查完毕还不能返回,下面就是等待或者阻塞的方法了。如果spins小于0,表示第一次进入循环。
- 调用spinsFor方法计算自旋次数,如果大于0,那么初始化randomYields那么初始化randomYields。继续下一次循环,从这里的看出来,初始化的spins=n(n>=0),那么上面的检查将会最多走n+2次。
- 到这里,表示spins被初始化了,如果spins大于0,表示还有剩余的自旋次数。
- spins自减1,表示自旋一次完毕。
- 根据生成的随机数的格式来判断当前线程是否让出cpu的执行权,主要是为了让新来的请求先去尝试主动匹配,在重新获取到cpu执行权之后会继续执行。
- 到这里,表示spins=0,即自旋完毕,可以阻塞了。如果waiter为null,那么waiter置为w,随后继续下一次循环,即在真正的阻塞之前再循环检查一次。
- 到这里,开始真正的阻塞。如果是超时阻塞,计算剩余超时时间nanos,如果nanos大于0,那么调用parkNanos阻塞nanos纳秒,直到因被匹配而唤醒或者被中断或者阻塞时间完毕都将在下一次循环中退出循环。
- 如果不是超时阻塞,那么调用park一直阻塞,直到因被匹配而唤醒或者被中断都将在下一次循环中退出循环。
- 首先是两个检查是否可以返回的if语句。获取s结点的此时的item值item,如果item不等于e(该请求原本的item),说明s结点被成功匹配了,此时已经在另一个请求中执行了casItem操作。
/**
* 位于LinkedTransferQueue中的方法
* 自旋 <--> 随机让权(yield) -> park阻塞 ,直到结点被匹配或者被等待取消
*
* @param s 等待的结点
* @param pred s前驱,如果没有前驱就是s自己,目前版本不可能是null
* @param e 对于入队、传递方法,e为指定元素,不能为null;对于出队方法,e为null
* @param timed 如果是超时等待,则为true
* @param nanos 超时等待的时间纳秒,仅在nanos为true是有用
* @return 如果被匹配,返回匹配的请求的item值;如果因为中断或者超时被取消等待,返回当前结点的e
*/
private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
//如果是超时操作,计算等待超时的时间点
final long deadline = timed ? System.nanoTime() + nanos : 0L;
//获取当前请求的线程w
Thread w = Thread.currentThread();
//自旋次数spins,初始化为-1
int spins = -1; // initialized after first item and cancel checks
//伪随机数生成器,用于随机让权的判断
ThreadLocalRandom randomYields = null; // bound if needed
/*开启一个死循环,等待被匹配或者取消*/
for (; ; ) {
/*首先是两个检查是否可以返回的if语句*/
//获取s结点的此时的item值item
Object item = s.item;
/*
* 这里的判断返回实际上和SynchronousQueue的公平模式的awaitFulfill的方法类似
* 这里判断满足返回的条件是通过item字段来判断的,而item字段又被用来表示生产请求或者消费请求,因此需要将他们区分开
*
* 如果s被匹配了:
* 如果s是生产请求,那么s.item从传递的数据e指向null;
* 如果s是消费请求,那么s.item从null指向传递的数据e,
* 上面这两种改变都使得新item和它们原本的item值e不一致,因此可以返回
* 如果s被取消了:
* s.item 从传递的数据e或者null 指向s自己
*
* 如果item不等于e,说明s被匹配成功了,或者被取消了,都可以返回
*/
//如果item不等于e,如果第一个if判断为真,说明s结点被成功匹配了
if (item != e) { // matched
// assert item != s;
//此时s的item肯定没有指向自己,因为下面的第二个if判断中如果CAS指向自己成功会直接进入if代码块而返回
//将s结点的item指向自己,将s的waiter指向null,清除多余的引用
//可以看到被匹配的结点不会被对应的线程移除,实际上会在另一个匹配的新来的请求中被移除
s.forgetContents(); // avoid garbage
//item转换为E类型并返回,返回匹配的请求的原始item值,这是因为被匹配而返回的出口。
return LinkedTransferQueue.<E>cast(item);
}
/*
* 如果w线程被中断了,或者超时等待时间到了还没有被匹配,那么表示结点可以取消等待了
* 那么调用casItem尝试CAS的将调用结点的item从e指向s,如果在第一个if和第二个if之间s结点被成功匹配了,
* 那么这个CAS将会失败,因为此时预期原值肯定不为e了,下一次循环将会在上一个if代码块中被返回
*/
//如果第二个if判断为真,说明s结点因为中断或者超时而被成功取消了
if ((w.isInterrupted() || (timed && nanos <= 0)) &&
s.casItem(e, s)) { // cancel
//尝试清理s结点,以及其他无效结点
unsplice(pred, s);
//返回e,即返回当前结点的e,这是因为被取消而返回的出口。
return e;
}
//如果spins小于0,表示第一次进入循环
if (spins < 0) { // establish spins at/near front
//调用spinsFor计算自旋次数,如果大于0,那么初始化randomYields,继续下一次循环
//从这里的看出来,初始化的spins=n(n>=0),那么上面的检查将会最多走n+2次
if ((spins = spinsFor(pred, s.isData)) > 0)
randomYields = ThreadLocalRandom.current();
}
/*到这里,如果spins大于0,表示还有剩余的自旋次数*/
else if (spins > 0) { // spin
//spins自减1,表示自旋一次完毕
--spins;
//根据生成的随机数的格式来判断当前线程是否让出cpu的执行权
if (randomYields.nextInt(CHAINED_SPINS) == 0)
Thread.yield(); // occasionally yield
}
/*
* 到这里,表示spins=0,即自旋完毕,可以阻塞了
* 如果waiter为null,那么waiter置为w,随后继续下一次循环,即在真正的阻塞之前再循环检查一次
* */
else if (s.waiter == null) {
s.waiter = w; // request unpark then recheck
}
/*开始阻塞,如果是超时阻塞*/
else if (timed) {
//计算剩余超时时间nanos
nanos = deadline - System.nanoTime();
//如果nanos大于0,那么调用parkNanos阻塞nanos纳秒,直到因被匹配而唤醒或者被中断或者阻塞时间完毕都将在下一次循环中退出循环。
if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
}
/*如果不是超时阻塞*/
else {
//那么调用park一直阻塞,直到因被匹配而唤醒或者被中断都将在下一次循环中退出循环。
LockSupport.park(this);
}
}
}
/**
* 位于LinkedTransferQueue中的方法
* 转换item的类型
*/
@SuppressWarnings("unchecked")
static <E> E cast(Object item) {
//转换类型
return (E) item;
}
/**
* 计算自旋次数
*
* @param pred 前驱
* @param haveData 当前结点的isData属性
* @return 次数,大于等于0
*/
private static int spinsFor(Node pred, boolean haveData) {
//如果MP为true,并且pred不为null,那么可以自旋
if (MP && pred != null) {
//如果pred的isData属性和当前请求的hashData属性不一致,且前驱结点被匹配或者取消
if (pred.isData != haveData) // phase change
return FRONT_SPINS + CHAINED_SPINS;
//如果前驱结点的isData属性和当前请求的hashData属性一致,并且前驱结点被匹配或者取消
if (pred.isMatched()) // probably at front
return FRONT_SPINS;
//到这里,表示前驱结点的isData属性和当前请求的hashData属性一致,并且前驱结点没有被匹配或者取消
//如果前驱结点的waiter等于null
if (pred.waiter == null) // pred apparently spinning
return CHAINED_SPINS;
//到这里,表示前驱结点正在被阻塞
}
//如果是单个可用线程,或者前驱没有被匹配或者取消,并且正在被阻塞,那么返回0
return 0;
}
2.3.5.1 unsplice移除结点
unsplice是位于LinkedTransferQueue中的方法,尝试将被取消的s结点移除队列,并清理垃圾数据。
如果被取消的结点s作为队尾结点,或者pred已经出队,或者s已经出队,那么结点是不会被清理的。
s被清理成功之后,如果前驱pred也被匹配或者取消了,那么对队列从head开始循环清除连续的被匹配或者取消的结点。之后如果pred和s都没有出队,说明它们位于队列中间,并且pred和s可能没有被清理干净,这样情况就用到了sweepVotes属性, 此时会判断sweepVotes如果达到32,那么调用一次sweep方法对整个队列的结点进行扫描清理,随后sweepVotes重置为0,如果没有达到那么sweepVotes自增1,方法结束。
利用到sweepVotes属性的情况其实很简单,那就是两个连续的中间结点被取消了,如下案例:
假如结点n1和n2都被取消了,并且在unsplice方法中两个结点对应的线程都执行到pred.casNext(s, n)之前,此时的结构为:
现在假如thread1先执行pred.casNext(s, n),执行完毕之后结构如下:
随后thread1线程中由于pred没有被取消或者匹配,那么该线程退出。现在thread2开始执行pred.casNext(s, n) ,执行完毕之后结构如下:
随后thread2线程中由于pred结点n1被取消或者匹配,会进入后续的步骤,就可能会用到sweepVotes。因为我们发现这种极端情况下,被取消的n2结点并没有被移除队列,仅仅移除了n1结点。
因此某个结点在被移除之后,会检查如果前驱结点也被匹配或者取消,此时会先尝试从head开始向后清理连续出现的被匹配或者取消的结点,清理结束之后如果pred或者s被移除了队列,那么也没啥问题,但是如果发现pred和s都没有出队列,那么此时需要使用sweepVotes记录这种情况可能出现的次数,即队列的中间某个结点被取消了但是没有被移除的情况。
当sweepVotes达到SWEEP_THRESHOLD(32)的时候,会对整个队列进行一个完整的清理!32是一个估计值,此时队列中可能存在较多的被取消且没被移除的结点,从而影响其他操作的效率。
/**
* 位于LinkedTransferQueue中的方法
* 尝试将s移除队列,并清理垃圾数据
* 如果被取消的结点s作为队尾结点,或者pred已经出队,或者s已经出队,那么该结点是不会被清理的。
*
* @param pred s前驱,如果没有前驱就是s自己,在awaitMatch的调用中不可能是null
* @param s 需要移除的结点
*/
final void unsplice(Node pred, Node s) {
//同样将s的item指向自己,将s的waiter指向null,清除多余的引用,实际上在外面awaitMatch方法的判断条件上s的item就指向自己了
s.forgetContents(); // forget unneeded fields
//如果pred不为null,并且pred不等于s,并且pred的后继还是s,即存在pred存在并且没被移除,那么尝试移除s结点
if (pred != null && pred != s && pred.next == s) {
//获取s的后继结点n
Node n = s.next;
/*
* 如果n为null,即s作为尾结点,那么直接进入if代码块进一步清理
* 或者n不为null,n也不等于s(s没有出队),那么尝试CAS的将pred的后继从s设置为n,如果成功,表示s被移除了(将会被GC回收),
* 继续判断如果pred也被匹配或者取消了,那么可以进入if代码块进一步清理,否则方法结束
*/
if (n == null ||
(n != s && pred.casNext(s, n) && pred.isMatched())) {
/*
* 开启一个死循环,尝试从head开始向后清理连续出现的被匹配或者取消的结点,
* 并更新head的指向,直到head没有被匹配或者取消或者队列为空
*/
for (; ; ) { // check if at, or could be, head
//h赋值为此时的head
Node h = head;
//如果pred还是指向head 或者 h就等于s 或者 h为null
if (h == pred || h == s || h == null)
//表示队列为空,直接返回
return; // at head or list empty
//如果h没有被匹配或者取消
if (!h.isMatched())
//那么break跳出循环
break;
//如果h被匹配或者取消了,获取h的后继hn
Node hn = h.next;
//如果hn为null
if (hn == null)
//表示队列为空,直接返回
return; // now empty
//如果hn不等于h,并且尝试CAS的将head从h指向hn成功
if (hn != h && casHead(h, hn))
//那么h出队列,h的后继指向自己,继续下一次循环
h.forgetNext(); // advance head
}
/*
* 到这一步,表示此时的h(head)没有被匹配或者取消,并且队列还存在结点,那么现在尝试清理后面的数据
*
* 如果pred的后继不等于pred,并且s的后继不等于s,说明pred和s结点还没有在上面的循环过程中清理,那么说明可能需要进行全队列清理
*/
if (pred.next != pred && s.next != s) { // recheck if offlist
//开启一个死循环
for (; ; ) { // sweep now if enough votes
//获取sweepVotes的值
int v = sweepVotes;
//如果此时的sweepVotes小于SWEEP_THRESHOLD(32)
if (v < SWEEP_THRESHOLD) {
//那么调用casSweepVotes方法将sweepVotes自增一
if (casSweepVotes(v, v + 1))
//break跳出死循环
break;
}
/*
* 如果此时的sweepVotes大于等于SWEEP_THRESHOLD(32),那么调用casSweepVotes方法将sweepVotes的值置为0
* CAS失败则进行下一次循环重试,CAS成功则调用sweep方法进行一次全队列的垃圾清理工作
*/
else if (casSweepVotes(v, 0)) {
sweep();
break;
}
}
}
}
}
}
/**
* 位于LinkedTransferQueue中的方法
* 从头结点开始对每个队列结点做一次清理
*/
private void sweep() {
/*
* 开启一个循环,重头到尾的清理队列,超过三个结点的队列至少保留两个结点
* 初始化条件:p设置为最新head
* 循环条件:如果p不为null,并且 s设置为p的后继,s也不为null,那么继续循环
*/
for (Node p = head, s, n; p != null && (s = p.next) != null; ) {
/*如果s没有被匹配或者取消*/
if (!s.isMatched())
// Unmatched nodes are never self-linked
//那么p设置为s,即向后推进,进行下一次循环
p = s;
/*
* s被匹配或者取消了
* n设置为s的后继,如果n为null,表示s作为队列尾结点,还不需要清理
*/
else if ((n = s.next) == null) // trailing node is pinned
//那么直接break结束循环,清理清理完毕
break;
/*
* s被匹配或者取消了,并且s的后继n不为null
* 如果s等于n,即s作为head被移除队列了,即遍历中断了,此时需要重新获取head
*/
else if (s == n) // stale
// No need to also check for p == s, since that implies s == n
//p设置为此时的head,相当于重新开始循环
p = head;
/*
* s被匹配或者取消了,并且s的后继n不为null,并且s的后继不等于自己,
* 那么尝试CAS的将p的后继重s置为n,此时s出队(将会被GC回收),继续下一次循环
*/
else
p.casNext(s, n);
}
}
2.4 入队操作
由于是无界队列,因此传统的入队操作都不会因为队列满了而被阻塞,但是如果在内存资源被耗尽时试图执行入队操作也将失败(导致OutOfMemoryError)。
2.4.1 offer(e)方法
public boolean offer(E e)
在该队列的尾部插入指定的元素。 由于队列无限制,此方法将永远不会返回false 。
如果指定的元素为null,那么抛出NullPointerException。
内部就是调用transfer方法实现,传递参数:
- e – e ,表示入队列,不能为null;
- haveData – true,表示带有数据的模式;
- how – ASYNC,表示不会阻塞的入队方法;
- nanos – 0,表示超时时间,但是这里没有意义。
/**
* 在该队列的尾部插入指定的元素。 由于队列无限制,此方法将永远不会返回false 。
*
* @return true
* @throws NullPointerException 如果指定的元素为null
*/
public boolean offer(E e) {
//调用xfer方法,传递 e、true、ASYNC、0
xfer(e, true, ASYNC, 0);
//永远返回true
return true;
}
2.4.2 put(e)方法
public void put(E e)
在该队列的尾部插入指定的元素。 由于队列无限制,此方法将永远不会阻塞。
如果指定的元素为null,那么抛出NullPointerException。
内部就是调用transfer方法实现,传递参数:
- e – e ,表示入队列,不能为null;
- haveData – true,表示带有数据的模式;
- how – ASYNC,表示不会阻塞的入队方法;
- nanos – 0,表示超时时间,但是这里没有意义。
/**
* 在该队列的尾部插入指定的元素。 由于队列无限制,此方法将永远不会阻塞。
*
* @throws NullPointerException 如果指定的元素为null
*/
public void put(E e) {
//调用xfer方法,传递 e、true、ASYNC、0
xfer(e, true, ASYNC, 0);
}
2.4.3 add(e)方法
public boolean add(E e)
在该队列的尾部插入指定的元素。由于队列无限制,这种方法永远不会抛出IllegalStateException或返回false 。
如果指定的元素为null,那么抛出NullPointerException。
内部就是调用transfer方法实现,传递参数:
- e – e ,表示入队列,不能为null;
- haveData – true,表示带有数据的模式;
- how – ASYNC,表示不会阻塞的入队方法;
- nanos – 0,表示超时时间,但是这里没有意义。
/**
* 在该队列的尾部插入指定的元素。由于队列无限制,这种方法永远不会抛出IllegalStateException或返回false 。
*
* @return true
* @throws NullPointerException如果指定的元素为null
*/
public boolean add(E e) {
//调用xfer方法,传递 e、true、ASYNC、0
xfer(e, true, ASYNC, 0);
//永远返回true
return true;
}
2.4.4 offer(e, timeout, unit)方法
public boolean offer(E e, long timeout, TimeUnit unit)
在该队列的尾部插入指定的元素。 由于队列无限制,此方法将永远不会阻塞或返回false 。应该忽略timeout 和unit参数,这个方法只为了兼容父接口BlockingQueue的同名抽象方法!
如果指定的元素为null,那么抛出NullPointerException。
内部就是调用transfer方法实现,传递参数:
- e – e ,表示入队列,不能为null;
- haveData – true,表示带有数据的模式;
- how – ASYNC,表示不会阻塞的入队方法;
- nanos – 0,表示超时时间,但是这里没有意义。
/**
* 在该队列的尾部插入指定的元素。 由于队列无限制,此方法将永远不会阻塞或返回false 。
* 应该忽略timeout 和unit参数,这个方法只为了兼容父接口BlockingQueue的同名抽象方法!
*
* @return true
* @throws NullPointerException 如果指定的元素为null
*/
public boolean offer(E e, long timeout, TimeUnit unit) {
//调用xfer方法,传递 e、true、ASYNC、0
xfer(e, true, ASYNC, 0);
//永远返回true
return true;
}
2.5 出队操作
虽然是无界队列,但是某些方法在出队的时候仍然需要判断队列是否为空,如果为空将可能被阻塞。
2.5.1 take()方法
public E take()
获取并移除此队列的头部,在队列没有入队、传递元素的结点或者没有入队、传递元素的请求之前一直等待。
如果等待被中断,那么抛出InterruptedException。
内部就是调用transfer方法实现,传递参数:
- e – null ,表示出队列;
- haveData – fasle,表示不带数据的模式;
- how – SYNC,表示阻塞的方法;
- nanos – 0,表示超时时间,但是这里没有意义。
在take过程中,同样会首先循环匹配队列的结点,如果匹配成功那么就可以返回匹配的结点的item值了,一定不会为null,否则会被构建为结点加入到队列尾部,等待后来的请求被匹配成功,随后匹配的请求的e值,或者在等待时被中断而返回,此时一定会返回null。
/**
* 获取并移除此队列的头部,在队列没有入队、传递元素的结点或者没有入队、传递元素的请求之前一直等待。
*
* @return 被移除的队列头部元素值
* @throws InterruptedException 如果等待被中断
*/
public E take() throws InterruptedException {
//调用xfer方法,传递 null、false、SYNC、0
E e = xfer(null, false, SYNC, 0);
//如果e不等于null,表示获取到了获取到了元素,那么返回e
if (e != null)
return e;
//否则,只能是因为等待被中断而返回的情况,抛出InterruptedException
Thread.interrupted();
throw new InterruptedException();
}
2.5.2 poll()方法
public E poll()
获取并移除此队列的头部,如果此队列为空,则返回 null 。
内部就是调用transfer方法实现,传递参数:
- e – null ,表示出队列;
- haveData – fasle,表示不带数据的模式;
- how – NOW,表示非阻塞的方法;
- nanos – 0,表示超时时间,但是这里没有意义。
/**
* 获取并移除此队列的头部,如果此队列为空,则返回 null。
*
* @return 被移除的队列头部元素值,如果此队列为空,则返回 null。
*/
public E poll() {
//调用xfer方法,传递 null、false、NOW、0,并且返回xfer方法的返回值
return xfer(null, false, NOW, 0);
}
2.5.3 poll(timeout, unit)方法
public E poll(long timeout,TimeUnit unit)
获取并移除此队列的头部,否则等待指定时间,等待时间之内如果没有入队、传递元素的请求来匹配成功,那么就返回null。
如果等待被中断,那么抛出InterruptedException。
内部就是调用transfer方法实现,传递参数:
- e – null ,表示出队列;
- haveData – fasle,表示不带数据的模式;
- how – TIMED,表示超时阻塞的方法;
- nanos – unit.toNanos(timeout),表示超时时间纳秒。
/**
* 获取并移除此队列的头部,否则等待指定时间,等待时间之内如果没有入队、传递元素的请求来匹配成功,那么就返回null。
*
* @param timeout 超时时间
* @param unit 时间单位
* @return 被移除的队头,如果等待时间结束还没有被匹配那么返回null
* @throws InterruptedException 如果在等待时被中断
*/
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
//调用xfer方法,传递 null、false、TIMED、unit.toNanos(timeout)
E e = xfer(null, false, TIMED, unit.toNanos(timeout));
//如果e不等于null,表示获取到了元素,那么返回e
//或者e等于null,但是当前线程没有被中断,表示因为等待时间结束还没有被匹配,那么返回e(null)
if (e != null || !Thread.interrupted())
return e;
//到这里表示e等于null并且当前线程被中断,抛出InterruptedException异常
throw new InterruptedException();
}
2.5.4 remove()方法
public E remove()
获取并移除此队列的头部,内部就是调用poll方法,内部的poll返回null就抛出一个NoSuchElementException异常。
/**
* 获取并移除此队列的头部,内部就是调用poll方法,内部的poll返回null就抛出一个NoSuchElementException异常。
*
* @return 队列头
* @throws NoSuchElementException 队列为空
*/
public E remove() {
//内部就是调用的poll方法,获取返回值x
E x = poll();
//如果x不为null,那么返回x
if (x != null)
return x;
//否则,抛出NoSuchElementException异常
else
throw new NoSuchElementException();
}
2.5.5 remove(o)方法
public boolean remove(Object o)
从此队列中移除指定元素的单个实例(如果存在,使用equals比较)。如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false。
删除指定元素的方法和其他方法都不一样,它没有使用xfer方法,而是具有自己的逻辑,实际上也比较简单,即从队列头开始遍历队列,查找具有和指定元素o使用equals比较返回true的item的等待的结点p,然后casItem的将p的item设置为null,这相当于p被手动匹配成功了,也就相当于删除了元素o!外部的删除方法内部就是调用的findAndRemove方法!
大概步骤为:
- 如果e不等于null,那么可以在队列中查找:
- 开启一个循环,初始化pred为null,用于保存结点前驱,初始化p为head,用于保存当前结点,如果p不等于null,那么继续循环:
- 获取p的item。如果p的isData属性为true,表示属于入队、传递元素操作,这样的结点才可能被删除:
- 如果item不为null,那么表示该结点没有被其他请求匹配(没有执行p.casItem(item, e));并且,如果item不等于p,说明该结点也没有被取消(没有执行s.casItem(e, s)),也没有被完全匹配(没有执行s.forgetContents());并且,如果指定元素e和p的item相等;并且,p调用tryMatchData尝试人为的匹配结点p,简单的说就是尝试将p的item指向null,如果成功返回true;以上四个判断都满足,都返回true,那么表示删除该元素成功,进入下一步:
- 类似于被取消的结点的情况,调用unsplice尝试将p移除链表。
- 返回true。
- 如果item不为null,那么表示该结点没有被其他请求匹配(没有执行p.casItem(item, e));并且,如果item不等于p,说明该结点也没有被取消(没有执行s.casItem(e, s)),也没有被完全匹配(没有执行s.forgetContents());并且,如果指定元素e和p的item相等;并且,p调用tryMatchData尝试人为的匹配结点p,简单的说就是尝试将p的item指向null,如果成功返回true;以上四个判断都满足,都返回true,那么表示删除该元素成功,进入下一步:
- 否则,如果item为null,表示p作为还没有被匹配的出队列结点,那么说明p的后面不存在入队、传递元素构成的结点,直接break跳出循环,不必继续查找了。
- 到这一步,表示:p结点的isData为false,并且item不为null,即p属于被匹配或者取消的出队结点,或者p结点的isData为true,即p属于入队、传递元素的模式,但是在里面的判断过程中可能p被其他请求匹配或者取消了,也可能p的item和指定元素不相等,而导致移除失败,那么需要继续向后查找。
- pred设置为p,p设置为p的后继。如果此时p等于pred,说明p被移除队列了,那么pred置为null,p设置为head,相当于重头开始循环。继续下一次循环。
- 获取p的item。如果p的isData属性为true,表示属于入队、传递元素操作,这样的结点才可能被删除:
- 开启一个循环,初始化pred为null,用于保存结点前驱,初始化p为head,用于保存当前结点,如果p不等于null,那么继续循环:
- 如果e等于null,或者队列遍历完毕也没有找到具有与指定元素相等的item的等待匹配的结点,那么返回false。
/**
* 从此队列中移除指定元素的单个实例(如果存在,使用equals比较)。
*
* @param o 指定元素
* @return 如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false。
*/
public boolean remove(Object o) {
//删除指定元素的内部方法实现
return findAndRemove(o);
}
/**
* 位于LinkedTransferQueue中的方法
* 删除指定元素的内部方法实现
*
* @param e 指定元素
* @return 如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false。
*/
private boolean findAndRemove(Object e) {
//如果e不等于null,那么可以查找
if (e != null) {
/*
* 开启一个循环
* 初始化条件:初始化pred为null,用于保存结点前驱,初始化p为head,用于保存当前结点
* 循环条件:如果p不等于null,那么继续循环
*/
for (Node pred = null, p = head; p != null; ) {
//获取p的item
Object item = p.item;
//如果p的isData属性为true,表示属于入队、传递元素操作,这样的结点才可能被删除
if (p.isData) {
/*
* 如果item不为null,那么表示该结点没有被其他请求匹配(没有执行p.casItem(item, e))
* 并且 如果item不等于p,说明该结点也没有被取消(没有执行s.casItem(e, s)),也没有被完全匹配(没有执行s.forgetContents())
* 并且 如果指定元素e和p的item相等
* 并且 p调用tryMatchData尝试人为的匹配结点,简单的说就是尝试将p的item指向null,如果成功返回true
*
* 以上四个判断都满足,都返回true,那么表示删除该元素成功
*/
if (item != null && item != p && e.equals(item) &&
p.tryMatchData()) {
//类似于被取消的结点的情况,调用unsplice尝试将p移除链表
unsplice(pred, p);
//返回true
return true;
}
}
/*
* 否则,如果item为null
* 表示p作为没有被匹配和取消的出队列结点,那么说明p的后面不存在入队、传递元素构成的结点
*/
else if (item == null)
//直接break跳出循环,不必继续查找了
break;
/*
* 到这一步,表示:
* 1 p结点的isData为false,并且item不为null,即p属于被匹配或者取消的出队结点
* 2 p结点的isData为true,即p属于入队、传递元素的模式,但是在里面的判断过程中
* 可能p被其他请求匹配或者取消了,也可能p的item和指定元素不相等,而导致移除失败
* 那么需要继续向后查找
*/
//pred设置为p
pred = p;
//p设置为p的后继,如果此时p等于pred,说明p被移除队列了
if ((p = p.next) == pred) { // stale
//那么pred置为null,p设置为head,相当于重头开始循环
pred = null;
p = head;
}
}
}
//如果e等于null,或者队列遍历完毕也没有找到具有与指定元素相等的item的等待匹配的结点,那么返回false
return false;
}
/**
* 位于Node的方法
* 尝试删除某个结点元素,简单的说就是尝试将item指向null,相当于被匹配了
*/
final boolean tryMatchData() {
// assert isData;
//获取调用结点的item,使用x保存
Object x = item;
//如果x不为null,那么表示该结点没有被其他请求匹配(没有执行p.casItem(item, e))
//并且 如果x不等于调用结点自身,说明该结点也没有被取消(没有执行s.casItem(e, s)),也没有被完全匹配(没有执行s.forgetContents())
//并且 调用casItem尝试cas的将调用结点的item值从x改为null成功,返回true
//以上三个条件都满足,都返回true,那么表示删除该元素成功
if (x != null && x != this && casItem(x, null)) {
//唤醒在该结点上阻塞等待的线程(如果有,就算没有也没关系)
//如果存在线程被唤醒(只有传递数据的方法会有线程阻塞,传统入队列方法没有),那么这个线程将会安全的返回(相当于传递数据成功,不会抛出异常)
LockSupport.unpark(waiter);
//返回true
return true;
}
//以上三个条件有一个不满足满足,就返回false,那么表示删除该元素失败
return false;
}
2.6 传递操作
传递操作是LinkedTransferQueue特有的操作,类似于SynchronousQueue的传递的方法,但是比SynchronousQueue更加灵活。
2.6.1 transfer(e)方法
public void transfer(E e)
如果队列存在等待接收元素消费者( take方法或超时poll方法),那么尝试将元素传输给它们,否则构造成结点加入队列尾部,当前线程一并等待,直到被新的消费者请求匹配接收或者线程等待被中断才能返回。
如果指定的元素为null,那么抛出NullPointerException,如果在等待时被中断,那么抛出InterruptedException,同时加入队列的结点会被舍弃。
内部就是调用transfer方法实现,传递参数:
- e – e ,表示传递元素,不能为null;
- haveData – true,表示带有数据的模式;
- how – SYNC,表示阻塞的方法;
- nanos – 0,表示超时时间,但是这里没有意义。
实际上这个而传输方法啊逻辑很简单,首先尝试主动匹配队列中的出队列的结点,如果匹配到了,那么出队列的请求会返回当前请求的e参数,即相当于传递数据成功,同时该传递元素的请求会返回null。
如果匹配失败,那么构造成结点加入队尾,随后该请求的结点以及线程都等待被后来的出队请求匹配,匹配成功出队列的请求返回当前结点的item参数,即相当于传递数据成功,同时该传递元素的请求会返回null。如果等待时被中断了,那么该传递元素的请求会返回传递的元素e,表示传递失败。
/**
* 如果队列存在等待接收元素消费者( take方法或超时poll方法),那么尝试将元素传输给它们,
* 否则构造成结点加入队列尾部,当前线程一并等待,直到被新的消费者请求匹配接收或者线程等待被中断才能返回。
*
* @throws NullPointerException 如果指定的元素为null
*/
public void transfer(E e) throws InterruptedException {
//调用xfer方法,传递e、true、SYNC、0,判断返回值是否不为null
if (xfer(e, true, SYNC, 0) != null) {
//如果不为null,表示传递失败,只是只可能是被中断情况,那么直接抛出InterruptedException异常
Thread.interrupted(); // failure possible only due to interrupt
throw new InterruptedException();
}
}
2.6.2 tryTransfer(e)方法
public boolean tryTransfer(E e)
如果队列存在等待接收元素消费者(take方法或超时poll方法),那么尝试将元素传传递它们并返回true,否则立即返回false,该传输的元素会被舍弃而不是加入队列。
如果指定的元素为null,那么抛出NullPointerException。
内部就是调用transfer方法实现,传递参数:
- e – e ,表示传递元素,不能为null;
- haveData – true,表示带有数据的模式;
- how – NOW,表示不会阻塞的其他方法;
- nanos – 0,表示超时时间,但是这里没有意义。
/**
* 如果队列存在等待接收元素消费者(take方法或超时poll方法),那么尝试将元素传传递它们并返回true,
* 否则立即返回false,该传输的元素会被舍弃而不是加入队列。
*
* @throws NullPointerException 如果指定的元素为null
*/
public boolean tryTransfer(E e) {
//调用xfer方法,传递 e、true、NOW、0,并且返回xfer方法的返回值
return xfer(e, true, NOW, 0) == null;
}
2.6.3 tryTransfer(e, timeout, unit)方法
public boolean tryTransfer(E e, long timeout, TimeUnit unit)
如果队列存在等待接收元素消费者(take方法或超时poll方法),那么尝试将元素传传递它们并返回true,否则等待指定时间,等待时间之内如果没有出队的请求来匹配成功,那么就返回false。
如果指定的元素为null,那么抛出NullPointerException,如果在等待时被中断,那么抛出InterruptedException,同时加入队列的结点会被舍弃。
内部就是调用transfer方法实现,传递参数:
- e – e ,表示传递元素,不能为null;
- haveData – true,表示带有数据的模式;
- how – SYNC,表示阻塞的方法;
- nanos – 0,表示超时时间,但是这里没有意义。
/**
* 如果队列存在等待接收元素消费者(take方法或超时poll方法),那么尝试将元素传传递它们并返回true,
* 否则等待指定时间,等待时间之内如果没有出队的请求来匹配成功,那么就返回false。
*
* @throws NullPointerException 如果指定的元素为null
* @throws InterruptedException 如果在等待时被中断
*/
public boolean tryTransfer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//调用xfer方法,传递 e、true、TIMED、unit.toNanos(timeout)
//如果e等于null,表示传递了元素,那么返回true
if (xfer(e, true, TIMED, unit.toNanos(timeout)) == null)
return true;
//如果e不等于null,表示没有传递元素,判断如果当前线程没有被中断,表示因为等待时间结束还没有被匹配,那么返回fasle
if (!Thread.interrupted())
return false;
//如果e不等于null,并且如果被中断,那么抛出InterruptedException异常
throw new InterruptedException();
}
2.7 检查操作
2.7.1 peek()方法
public E peek()
获取但不移除此队列的头;如果此队列为空,则返回 null。
有点类似于remove(o)的逻辑,但是少了移除操作,源码比较简单,就是遍历队列然后找到第一个没有被匹配或者取消的入队、传递操作结点然后返回结点的item值,没找到就表示队列为空,返回null。
/**
* @return 获取但不移除此队列的头;如果此队列为空,则返回 null。
*/
public E peek() {
//内部调用firstDataItem方法
return firstDataItem();
}
/**
* @return 返回第一个没有被匹配或者取消的入队、传递操作结点的item值,没找到就返回null
*/
private E firstDataItem() {
/*
* 开启一个循环,遍历整个队列,返回第一个没有被匹配或者取消的入队、传递操作结点的item,有些类似于remove(o)的逻辑
* 初始化条件:初始化p为head,用于保存当前结点
* 循环条件:如果p不等于null,那么继续循环
* 控制条件:p设置为p的后继(如果p没有出队)或者p设置为head(如果p已出队)
*/
for (Node p = head; p != null; p = succ(p)) {
//获取p的item
Object item = p.item;
//如果p的isData属性为true,表示属于入队、传递元素操作,这样的结点才可能被返回
if (p.isData) {
/*
* 如果item不为null,那么表示该结点没有被其他请求匹配(没有执行p.casItem(item, e))
* 并且 如果item不等于p,说明该结点也没有被取消(没有执行s.casItem(e, s)),也没有被完全匹配(没有执行s.forgetContents())
*
* 以上两个判断都满足,都返回true,那么表示该结点没有被匹配和删除
*/
if (item != null && item != p)
//那么返回结点的item
return LinkedTransferQueue.<E>cast(item);
}
/*
* 否则,如果item为null
* 表示p作为没有被匹配和取消的出队列结点,那么说明p的后面不存在入队、传递元素构成的结点
*/
else if (item == null)
//直接break跳出循环,不必继续查找了
return null;
}
//如果队列遍历完毕也没有找到没有被匹配或者取消的入队、传递操作结点,那么返回null
return null;
}
/**
* 如果p的后继不等于自己,即p没有出队,那么返回p的后继
* 否则,返回最新的head,即重头开始
*/
final Node succ(Node p) {
Node next = p.next;
return (p == next) ? head : next;
}
2.7.2 element()方法
public E element()
获取但是不移除此队列的头。此方法与 peek 唯一的不同在于此队列为空时将抛出一个异常。
/**
* 获取,但是不移除此队列的头。此方法与 peek 唯一的不同在于:此队列为空时将抛出一个异常。
*
* @return 队头
* @throws NoSuchElementException 如果此队列为空
*/
public E element() {
//内部调用peek方法获取返回值x
E x = peek();
//如果x不为null,那么返回x;否则抛出NoSuchElementException异常
if (x != null)
return x;
else
throw new NoSuchElementException();
}
2.7.3 hasWaitingConsumer()方法
public boolean hasWaitingConsumer()
如果存在至少一个通过take或超时poll方法正在等待接收元素的出队结点,则返回true,否则返回false。
/**
* @return 如果存在至少一个通过take或超时poll方法正在等待接收元素的出队结点,则返回true,否则返回false。
*/
public boolean hasWaitingConsumer() {
//调用firstOfMode方法,传递false-出队模式,如果返回值不等于null,表示存在
return firstOfMode(false) != null;
}
/**
* @return 返回给定模式的第一个没有匹配或者取消的结点,没找到就返回null
* @date 给定模式 false 出队 true 入队、传递
*/
private Node firstOfMode(boolean isData) {
/*
* 开启一个循环,遍历整个队列,返回给定模式的第一个没有匹配或者取消的结点
* 初始化条件:初始化p为head,用于保存当前结点
* 循环条件:如果p不等于null,那么继续循环
* 控制条件:p设置为p的后继(如果p没有出队)或者p设置为head(如果p已出队)
*/
for (Node p = head; p != null; p = succ(p)) {
//如果p没有被匹配或者取消
if (!p.isMatched())
//如果p的模式和指定模式相等,那么返回p,否则直接返回null,这里的逻辑和isEmpty的逻辑一致
//如果队列中有一个没有被匹配的或者取消的入队、传递结点(isData为true),那么就不会存在没有被匹配或者取消的出队结点
//如果队列中有一个没有被匹配的或者取消的出队结点(isData为false),那么就不会存在没有被匹配或者取消的入队、传递结点
return (p.isData == isData) ? p : null;
}
//循环完毕没有返回,那么直接返回null
return null;
}
2.8 size操作
2.8.1 size/getWaitingConsumerCount方法
public int size()
返回此队列中元素的数量,即返回此队列中没有被取消或者匹配的入队、传递元素的结点的数量估计,如果此队列包含超过Integer.MAX_VALUE元素,则返回Integer.MAX_VALUE。
public int getWaitingConsumerCount()
返回此队列中通过take或超时poll方法正在等待接收元素的出队结点数量的估计,如果此队列包含超过Integer.MAX_VALUE元素,则返回Integer.MAX_VALUE。
/**
* @return 返回此队列中元素的数量,即返回此队列中没有被取消或者匹配的入队、传递元素的结点的数量估计
* 如果此队列包含超过Integer.MAX_VALUE元素,则返回Integer.MAX_VALUE
*/
public int size() {
//内部调用countOfMode方法
return countOfMode(true);
}
/**
* @return 返回此队列中通过take或超时poll方法正在等待接收元素的出队结点数量的估计
* 如果此队列包含超过Integer.MAX_VALUE元素,则返回Integer.MAX_VALUE
*/
public int getWaitingConsumerCount() {
return countOfMode(false);
}
/**
* 队列的计数操作,size 和 getWaitingConsumerCount 都调用该方法
*
* @param data 模式,true表示返回此队列中没有被取消或者匹配的入队、传递元素的结点的数量估计,
* false表示队列中通过take或超时poll方法正在等待接收元素的出队结点数量的估计
*/
private int countOfMode(boolean data) {
//初始化计数器为0
int count = 0;
/*
* 开启一个循环,遍历整个队列,进行计数操作
* 初始化条件:初始化p为head,用于保存当前结点
* 循环条件:如果p不等于null,那么继续循环
*/
for (Node p = head; p != null; ) {
//如果p结点没有被匹配或者取消,那么可能可以参加计数
if (!p.isMatched()) {
//如果p的isData不等于data方法参数,那么直接返回0
//即如果遇到了另一种模式的正在等待的结点,那么说明肯定没有此模式的正在等待的结点
if (p.isData != data)
return 0;
//否则count自增1,如果到了Integer.MAX_VALUE,那么也直接返回
if (++count == Integer.MAX_VALUE) // saturated
break;
}
//获取p的后继
Node n = p.next;
/*如果p没有出队*/
if (n != p)
//那么p赋值为后继,向后推进
p = n;
/*如果p已出队*/
else {
//count重置为0,p重置为head,相当于重新开始循环
count = 0;
p = head;
}
}
//for循环结束,返回count值
return count;
}
2.8.2 isEmpty()方法
public boolean isEmpty()
如果此队列不包含元素,即如果此队列不包含没有被匹配的或者取消的入队、传递结点
则返回true,否则返回false。
/**
* @return 如果此队列不包含元素,即如果此队列不包含没有被匹配的或者取消的入队、传递结点
* 则返回true,否则返回false
*/
public boolean isEmpty() {
/*
* 开启一个循环,遍历整个队列,查找没有被匹配或者取消的入队、传递操作结点的item
* 初始化条件:初始化p为head,用于保存当前结点
* 循环条件:如果p不等于null,那么继续循环
* 控制条件:p设置为p的后继(如果p没有出队)或者p设置为head(如果p已出队)
*/
for (Node p = head; p != null; p = succ(p)) {
//如果p没有被匹配或者取消
if (!p.isMatched())
//那么直接返回!p.isData,即如果isData为true,则返回false,如果isData为false,则返回true,因为:
//如果队列中有一个没有被匹配的或者取消的入队、传递结点(isData为true),那么就不会存在没有被匹配或者取消的出队结点,就可以返回false
//如果队列中有一个没有被匹配的或者取消的出队结点(isData为false),那么就不会存在没有被匹配或者取消的入队、传递结点,就可以返回true
return !p.isData;
}
//循环完毕没有返回,那么直接返回true
return true;
}
2.9 迭代操作
public Iterator< E > iterator()
返回在队列中的元素上按适当顺序进行迭代的迭代器Iterator。和其他并发容器一样,返回的 Iterator 是一个“弱一致”的,不会抛出 ConcurrentModificationException,即支持并发修改,但是不保证迭代获取的元素就是此时队列中的元素!
下面的源码解析包括迭代器的方法,实际上还是很简单的,弱一致性的原理在注释中也讲的很清楚,实际下一次要返回的值在上一次迭代的时候就确定了,使用nextNode和nextItem保存,相当于值的快照,弱一致性是很显而易见的!
/**
* 返回在队列中的元素上按适当顺序进行迭代的迭代器Iterator。
* 和其他并发容器一样,返回的 Iterator 是一个“弱一致”的,不会抛出 ConcurrentModificationException,
* 即支持并发修改,但是不保证迭代获取的元素就是此时队列中的元素!
*/
public Iterator<E> iterator() {
//返回一个Itr对象实例
return new Itr();
}
/**
* “弱一致”的迭代器的内部实现,作为LinkedTransferQueue的内部类
*/
final class Itr implements Iterator<E> {
//下一个下第二代返回的Node结点快照
private Node nextNode; // next node to return item for
//下一个要迭代返回的结点的item值快照
private E nextItem; // the corresponding item
//最后一次迭代的结点,用于辅助删除
private Node lastRet; // last returned node, to support remove
//最后一次迭代的结点的前驱,用于辅助删除
private Node lastPred; // predecessor to unlink lastRet
/**
* 移动到下一个迭代的结点并且设置相关参数的方法,在初始化迭代器和next方法中被调用
*
* @param prev 结点的前驱
*/
private void advance(Node prev) {
//r保存lastRet,b保存lastPred
Node r, b; // reset lastPred upon possible deletion of lastRet
/*如果lastRet不为null 并且 lsatRet没有被匹配或者取消*/
if ((r = lastRet) != null && !r.isMatched())
//那么lastPred赋值为lastRet
lastPred = r; // next lastPred is old lastRet
/*否则,b赋值为lastPred,如果b为null 或者 b被取消或者匹配了*/
else if ((b = lastPred) == null || b.isMatched())
//那么lastPred赋值为null,在第一次迭代初始化数据的时候走这个逻辑
lastPred = null; // at start of list
/*否则*/
else {
Node s, n; // help with removal of lastPred.next
while ((s = b.next) != null &&
s != b && s.isMatched() &&
(n = s.next) != null && n != s)
b.casNext(s, n);
}
//最后一次迭代的结点赋值为prev
this.lastRet = prev;
/*
* 开启一个循环,p初始化为prev,初始化s、n
*/
for (Node p = prev, s, n; ; ) {
//如果p为null,那么s赋值为head,否则s赋值为p的后继
s = (p == null) ? head : p.next;
/*如果s为null,表示队列为空或者p的后继为空,即遍历到队列末尾*/
if (s == null)
//直接break跳出循环
break;
/*否则,如果s等于p,即p被出队列了*/
else if (s == p) {
//那么p设置为null,continue结束本次循环,继续下一次循环,下一次循环中由于p为null,s将被赋值为head
p = null;
continue;
}
//获取s的item值item
Object item = s.item;
//如果p的isData属性为true,表示属于入队、传递元素操作,这样的结点才可能被迭代
if (s.isData) {
/*
* 如果item不为null,那么表示该结点没有被其他请求匹配(没有执行p.casItem(item, e))
* 并且 如果item不等于s,说明该结点也没有被取消(没有执行s.casItem(e, s)),也没有被完全匹配(没有执行s.forgetContents())
*
* 以上两个判断都满足,都返回true,那么表示该结点没有被匹配和删除,那么该结点就是啊下一个要被迭代的结点
*/
if (item != null && item != s) {
//nextItem赋值为s结点的item
nextItem = LinkedTransferQueue.<E>cast(item);
//nextNode赋值为s结点
nextNode = s;
//方法结束
return;
}
}
/*
* 否则,如果item为null
* 表示s作为没有被匹配的出队列结点,那么说明s的后面不存在入队、传递元素构成的结点
*/
else if (item == null)
//直接break跳出循环,不必继续查找了
break;
// assert s.isMatched();
/*
* 到这一步,表示:
* 1 s结点的isData为false,并且item不为null,即s属于被匹配或者取消的出队结点
* 2 s结点的isData为true,即s属于入队、传递元素的模式,但是s可能被其他请求匹配或者取消了
*
* 以上两种情况满足一种,那么需要继续向后查找
*/
/*如果p为null,此时s是最开始的head*/
if (p == null)
//那么p赋值为s,继续下一次循环,向后推推进
p = s;
/*否则,n赋值为s的后继,如果n为null,表示遍历到队列末尾*/
else if ((n = s.next) == null)
//直接break跳出循环
break;
/*否则,如果s等于n,表示s结点出队了*/
else if (s == n)
//那么p设置为null,继续下一次循环,下一次循环中由于p为null,s将被赋值为head
p = null;
/*否则,表示s结点被匹配或者取消了,*/
else
//尝试cas的将p结点的后继重s设置为n,s出队列,继续下一次循环
p.casNext(s, n);
}
//如果上面的循环结束,方法还没有返回,说明迭代到了队列末尾或者队列为空
//那么nextNode和nextItem都置为null,表示迭代完毕,下一次的hasNext方法将返回false
nextNode = null;
nextItem = null;
}
/**
* 构造器
*/
Itr() {
/*
* 调用advance方法,传入null,用于初始化属性
* nextNode被初始化为第一个没有被取消或者匹配的入队、传递结点,没找到就是null
* nextItem被初始化为第一个没有被取消或者匹配的入队、传递结点的item,没找到就是null
* lastRet被初始化为null
* nextNode被初始化为null
*/
advance(null);
}
/**
* @return 是否还有可迭代的数据,true 是 false 否
*/
public final boolean hasNext() {
//判断nextNode是否不为null就行了
return nextNode != null;
}
/**
* 返回要迭代的数据,并且获取下一次要迭代的数据
*/
public final E next() {
//p赋值为nextNode
Node p = nextNode;
//如果p为null,表示要迭代的数据不存在,抛出NoSuchElementException异常
if (p == null) throw new NoSuchElementException();
//e赋值为nextItem
E e = nextItem;
//调用advance,将p结点作为前驱,获取下一次要迭代的数据
advance(p);
//返回e,这就是弱一致性的由来,这里返回的实际上是上一次就保存好的数据快照
return e;
}
/**
* 移除最后一次迭代的结点,这个方法不做任何保证
*/
public final void remove() {
//获取lastRet
final Node lastRet = this.lastRet;
//如果lastRet为null,表示最后迭代的结点已经被移除了,或者还没有开始迭代,直接抛出IllegalStateException异常
if (lastRet == null)
throw new IllegalStateException();
//lastRet重置为null,表示最后迭代的结点已经被移除了,这里可以看出如果连续调用remove方法肯定会抛出异常
this.lastRet = null;
//这里就和remove(o)差不多了,lastRet调用tryMatchData,即尝试主动匹配,相当于lastRet结点被匹配了
//tryMatchData的源码在remove(o)部分也讲过了,简单的说就是尝试将item指向null,相当于被匹配了
if (lastRet.tryMatchData())
//如果成功,类似于被取消的结点的情况,调用unsplice尝试将p移除链表
unsplice(lastPred, lastRet);
//无论成功还是失败,都直接返回
}
}
2.10 执行流程
下面通过图解,来演示一下各种基本情况。
2.10.1 初始化
首先是新建一个LinkedTransferQueue对象的时候,实际上内部队列没有初始化。
2.10.2 第1步:put(e)操作
线程t1调用了put(e)方法,尝试将a入队。
此时我们知道请求是不能匹配成功的,因此只能新建Node结点并调用tryAppend入队。由于队列tail和head都为null,因此相当于初始化队列,此时,根据tryAppend源码,明显head会指向该节点,而tail还是null:
由于是put方法,因此虽然结点入队列了,但是该请求可以返回,即线程不会阻塞。
2.10.1 第3步:put(e)操作
线程t1调用了put(e)方法,尝试将b入队。
此时我们知道请求也是不能匹配成功的,因此只能新建Node结点s并调用tryAppend入队。在tryAppend方法的循环中,初始化的t=tail,p=t,但是tail为null,因此p = head,此时head不为null,因此从head开始向后查找:
此时p的后继为null,因此我们会让新结点s成为p指向的结点的后继:
注意,在追加成功之后,会尝试改变tail的指向。
此时明显p不等于t,tail等于t,因此会调用casTail(t, s),即将tail指向s结点:
由于是put方法,因此虽然结点入队列了,但是该请求可以返回,即线程不会阻塞。返回之后的队列结构为:
2.10.1 第3步:put(e)操作
线程t1调用了put(e)方法,尝试将c入队。
此时我们知道请求也是不能匹配成功的,因此只能新建Node结点s并调用tryAppend入队。在tryAppend方法的循环中,初始化的t=tail,p=t:
随后会让s结点成为p的后继:
注意,在追加成功之后,会尝试改变tail的指向。
此时明显p等于t,因此不会改变tail的指向。由于是put方法,虽然结点入队列了,但是该请求可以返回,即线程不会阻塞。返回之后的队列结构为:
可以发现,这就是tail不一定是尾结点的原理。
2.10.1 第4步:poll()操作
线程t1调用了poll()方法,尝试出队操作。
在匹配的时候,它会从head开始查找,h=head,p=h:
随后我们知道,p没有被取消也没有被匹配,并且p的isData为true,肯定和当前请求的haveData(false)不一致,这表明当前请求可以和p结点匹配:
匹配就是调用p.casItem(item, e)方法,item为a,e为null,成功之后的结构如下:
这就表示p结点被匹配成功了,随后会尝试查找并移除被匹配或者取消的结点,以及更新head指向,将q=p:
此时明显q等于h,因此被匹配的结点并没有移除,也不会改变head。最终该请求会返回匹配的结点的元素item,即a,方法结束。最终结构为:
2.10.1 第5步:poll()操作
线程t1调用了poll()方法,尝试出队操作。
在匹配的时候,它会从head开始查找,h=head,p=h:
此时(item != null) == isData 这个而判断将返回false,表明p结点被匹配了,随后会将p向后推进,继续匹配:
第二轮循环中,p结点没有被匹配和取消,并且p结点和当请求的isData模式不一致,因此可以匹配。
匹配就是调用p.casItem(item, e)方法,item为b,e为null,成功之后的结构如下:
这就表示p结点被匹配成功了,随后会尝试查找并移除被匹配或者取消的结点,以及更新head指向,将q=p:
此时明显q不等于h,随后获取q的后继n,此时head还是等于h,那么会改变head的指向: casHead(h, n == null ? q : n),n不为null,成功之后结构如下:
然后h指向的结点(原head结点)将会出队列,h的后继指向自己。移除结点以及改变head操作结束,最终该请求会返回匹配的结点的元素item,即b,方法结束。最终结构为:
我们可以看到,这就是前面说的head不是指向队列真正的头部,并且tail指向的结点位于head指向的结点之前的情况。
实际上这种情况只是暂时的,它会随着后续结点的添加而变成正常情况!
2.10.2 第6步:transfer(e)操作
线程t1调用了transfer(e)方法,尝试传递元素操作,尝试传递d。
在最开始匹配的时候,它会从head开始查找,此时我们知道请求也是不能匹配成功的,因此只能新建Node结点s并调用tryAppend入队。在tryAppend方法的循环中,初始化的t=tail,p=t:
随后调用p.cannotPrecede方法,此时可以追加,继续获取p的后继n,此时n明显不为null,因此会向后推进查找,p=n,继续下一次循环:
第二次循环中,p的后继为null,因此会让s结点成为p的后继:
追加成功之后,会尝试改变tail引用的指向,明显p不等于t,tail还是等于t,因此会调用casTail(t, s),即将tail指向s结点:
tryAppend返回之后,由于是阻塞的方法,因此该线程会调用awaitMatch继续等待,直到被匹配、被唤醒、等待被中断才会返回。假设当前请求的t1线程最终被阻塞了,那么结构如下:
这里原来的tail结点虽然没有手动清理next引用,但是根据可达性分析算法,这个结点既不是GC root结点也不在其他GC root结点的引用链上,最终会被GC回收。
3 LinkedTransferQueue的总结
JDK1.7的LinkedTransferQueue综合了JDK1.5的LinkedBlockingQueue和SynchronousQueue的特点,又做出了改进:
相比于LinkedBlockingQueue,同样使用队列结构来存放入队列的结点。LinkedTransferQueue底层没有使用锁来控制同步,而是使用的CAS操作,性能更好。LinkedBlockingQueue实际上最大长度为Integer.MAX_VALUE,而LinkedTransferQueue做到了真正的“无界”,只会受到内存的限制。
另外,在LinkedBlockingQueue中,如果此时有消费者线程在等待消费,即使来了新的入队操作,指定元素仍然会被构建为结点并加入队列,然后唤醒消费者线程又将结点出队列。而在LinkedTransferQueue中,如果此时有消费者线程在等待消费,来了新的入队操作之后,我们看xfer方法源码就知道,入队操作会首先尝试匹配等待的结点,如果匹配消费者结点成功,那么直接将指定元素传递给等待的消费者结点,随后就返回了,省去了包装结点并入队的操作,效率更高。
相比于SynchronousQueue,SynchronousQueue的传统入队方法实际上就是LinkedTransferQueue的transfer系列方法,另外SynchronousQueue不能存储元素,而LinkedTransferQueue的传统入队方法则可以存储元素(通过追加结点)并且会阻塞线程,因此LinkedTransferQueue的功能更强大。
在学习LinkedTransferQueue的时候,我建议先学习LinkedBlockingQueue和SynchronousQueue,特别是SynchronousQueue,不然直接学习LinkedTransferQueue的源码可能会比较困难!
相关文章:
- LinkedBlockingQueue:JUC—LinkedBlockingQueue源码深度解析。
- SynchronousQueue:JUC—三万字的SynchronousQueue源码深度解析。
- LockSupport:JUC—LockSupport以及park、unpark方法底层源码深度解析
- volatile:Java中的volatile实现原理深度解析以及应用。
- CAS:Java中的CAS实现原理解析与应用。
- UNSAFE:JUC—Unsafe类的原理详解与使用案例。
如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!