Queue常用类解析之PriorityQueue
Queue常用类解析之ConcurrentLinkedQueue
Queue常用类解析之BlockingQueue(一):PriorityBlockingQueue、DelayQueue和DelayedWorkQueue
Queue常用类解析之BlockingQueue(二):ArrayBlockingQueue
Queue常用类解析之BlockingQueue(三):LinkedBlockingQueue
Queue常用类解析之BlockingQueue(四):SynchronousQueue
接着上文对BlockingQueue的介绍继续向下
TransferQueue是jdk1.7新增的一个接口,是阻塞队列LinkedBlockingQueue的子接口。
TransferQueue适用于生产者必须等待消费者进行接收数据的阻塞队列场景。
在普通的BlockingQueue中,入队操作和出队操作彼此分离,彼此都只和队列打交道,只需要往队列中存取数据,不需要关心对方的存在。而在TransferQueue中是一种生产者-消费者的应用场景,生产者和消费者之间并不是毫无关系的,生成者生成数据需要等待着一个消费者前来接受,否则就是一次失败的入队操作。因此在TransferQueue的入队和出队操作以外,还有一个更重要的操作概念称为transfer,指生产者将数据传递给消费者的过程。
transfer方法表示生产者传递数据给消费者进行消费,tryTransfer则表示在非阻塞或者限时阻塞的情况下这一过程是否成功。
TransferQueue也可以具有一个固定的容量,事实上SynchronousQueue就是一个典型的容量为0的TransferQueue,虽然其并没有实现TransferQueue接口,在容量为0的TransferQueue中,只有消费者接收了数据才能put成功,因此put操作和transfer操作是同义的。
TransferQueue定义了5个方法。
getWaitingConsumerCount和hasWaitingConsumer都与队列中的等待的消费者有关,前者返回等待的消费者数目,后者返回是否有等待的消费者。
tranfer和两个tryTransfer方法都用于transfer操作,分别是阻塞方法,非阻塞方法和限时阻塞方法。
final boolean isData; // false if this is a request node
volatile Object item; // initially non-null if isData; CASed to match
volatile Node next;
volatile Thread waiter; // null until waiting
和SychronousQueue$TransferQueue#QNode类基本一致,有4个属性。
同样的也有3个特殊状态:
item值为自身:取消状态,表示指定时间内没有找到匹配节点或者线程被中断。被匹配成功时逻辑处理完成后也会将item置为自身。
isData为true但是item为null:不是request节点,也不是data节点,表示一种逻辑节点,一般用于head节点
上述状态的节点称为matched节点,上述状态以外的节点为unmatched节点,是真正需要被匹配的节点。
next值为自身:表示节点从链表中移除,称为offList,在遍历过程中,该类型的节点的下一个节点被视为head节点而不是节点的next节点。
对于item属性,request节点时item为null,data节点时item为数据的具体值,被成功匹配时item为匹配节点的item值,被成功匹配时的匹配逻辑执行完成后为节点自身,取消时为节点自身。
LinkedTransferQueue是一个基于链表结果的无界的TransferQueue。
和大多数的集合不同,由于LinkedTransferQueue中存在着异步操作,因此size()方法并不是一个O(1)的方法,而是需要对集合进行遍历,而且由于遍历过程中队列可能被修改,其返回的结果也并不一定是实时一致的。另外,批量方法的操作也无法保证原子性。
队列中并不直接存储元素,而是封装成data节点(put)或request节点(take),当一个data节点入队时,如果遇到request节点,进行匹配并将节点出队,反之亦然。
由于LinkedTransferQueue是无界队列,所以所以的入队方法(add,put,offer)都是成功的。
/** head of the queue; null until first enqueue */
//头结点,head.next是队列中的第一个有效节点
transient volatile Node head;
/** tail of the queue; null until first append */
//尾节点,队列中的最后一个有效节点
private transient volatile Node tail;
/** The number of apparent failures to unsplice removed nodes */
//unsplice方法出的失败次数
private transient volatile int sweepVotes;
head表示第一个节点,head可能是matched节点,那么此时head.next是第一个unmatched节点,tail最后一个有效节点(unmatch节点)。一般希望head.next,如下图第一张。
但是LinkedTransferQueue允许更新的滞后性,存在更新的滞后性,如下图第二张。
private E xfer(E e, boolean haveData, int how, long nanos) {
if (haveData && (e == null)) //参数校验
throw new NullPointerException();
Node s = null; // the node to append, if needed
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 //选取unmatch节点尝试匹配
if (isData == haveData) // can't match //节点模式相同(都是request节点或者都是data节点),不能匹配,离开内循环
break;
if (p.casItem(item, e)) { // match 匹配成功
for (Node q = p; q != h;) {
Node n = q.next; // update by 2 unless singleton
//h依旧是head节点,说明没有其它线程干扰,那么从head节点开始到q都是matched节点,可以从链表中移除
//以q.next(不存在则选取q)作为新的head节点
if (head == h && casHead(h, n == null ? q : n)) {
//head节点修改成功,原来的head节点修改成offlist类型,因为遍历线程可以将head节点作为offlist节点的遍历过程的下一个节点
h.forgetNext();
break;
} // advance and retry //其它线程对head节点进行了修改
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
//head节点不存在 || head.next不存在 说明链表中没有有意义的节点(request节点和data节点都不存在),不需要修改head节点
// head.next是unmatched节点,head节点已经不需要修改
break; // unless slack < 2
}
//唤醒线程
LockSupport.unpark(p.waiter);
//返回被匹配节点的item属性
return LinkedTransferQueue.cast(item);
}
}
Node n = p.next;
p = (p != n) ? n : (h = head); // Use head if p offlist //从下个节点继续匹配,offlist节点则以头结点作为下一个节点
}
//没有unmatched节点或者所有节点模式相同(都是request节点或者都是data节点)
//NOW模式不做任何处理
if (how != NOW) { // No matches available
if (s == null)
s = new Node(e, haveData);
//节点加入到链表
Node pred = tryAppend(s, haveData);
//加入失败,其他线程对链表进行了修改,插入了可以匹配的节点,重试匹配逻辑
if (pred == null)
continue retry; // lost race vs opposite mode
if (how != ASYNC)
//SYNC和TIMED模式还需要通过自旋/阻塞进行等待
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
//NOW和ASYNC模式以及TIMED模式超时后的返回
return e; // not waiting
}
}
整体流程为找到链表中的第一个unmatched节点,如果该节点模式是不同的,尝试进行匹配,匹配成功后更新头结点。如果匹配,则重试。
如果链表中不存在unmatched节点或者节点模式相同无法匹配,则根据参数how模式的不同进行相应的处理。
/*
* Possible values for "how" argument in xfer method.
*/
private static final int NOW = 0; // for untimed poll, tryTransfer
private static final int ASYNC = 1; // for offer, put, add
private static final int SYNC = 2; // for transfer, take
private static final int TIMED = 3; // for timed poll, tryTransfer
xfer方法时LinkedTransferQueue的核心方法,transfer,tryTransfer以及入队、出队方法的逻辑都由该方法完成。
xfer方法的how参数,共4种模式:
非阻塞方法(无参的poll方法和tryTransfer方法)为NOW模式,匹配不成功认为失败,不做任何处理
对于入队的offer, put和add为ASYNC模式,异步处理,匹配不成功加入队列,不需要自旋/阻塞方式以等待匹配,直接返回
阻塞方法(transfer和take方法)为SYNC模式,同步处理,匹配不成功加入队列,匹配成功前一直自旋/阻塞
限时阻塞方法(带时间参数的poll和tryTransfer方法)为TIMED,匹配不成功加入队列,匹配成功前自旋/阻塞一段时间
private Node tryAppend(Node s, boolean haveData) {
for (Node t = tail, p = t;;) { // move p to last node and append
Node n, u; // temps for reads of next & tail
//p是null节点,将head赋给p。p为null时有两种场景:
//1. tail为null,此时head也为null或者只有head一个节点
//2. 在后续逻辑中,p为offList时置为null,即遍历到offList节点时从head节点重新遍历
if (p == null && (p = head) == null) { //head节点不存在,新节点作为head
if (casHead(null, s))
return s; // initialize
}
else if (p.cannotPrecede(haveData)) //tail节点为unmatched节点且与待插入节点的模式不同,可以进行匹配
return null; // lost race vs opposite mode
else if ((n = p.next) != null) // not last; keep traversing // p不是最后一个节点,重新确定p值
//p != t(p从tail节点开始已经遍历完成了一个节点)且tail节点发生了变化,从tail节点开始重新遍历
//否则(p刚从tail节点开始遍历或者tail节点没有发生变化),继续向下遍历。如果p是offList节点,则置为null从头开始(下一步的循环中会从head取值)
//看不明白为什么要加 p != t的判断,为什么不是tail发生变化即取 p = tail ?
p = p != t && t != (u = tail) ? (t = u) : // stale tail
(p != n) ? n : null; // restart if off list
//CAS方式将s追加到p节点后面,失败,继续向下遍历
else if (!p.casNext(null, s))
p = p.next; // re-read on CAS failure
//s追加到p节点后面,更新tail
else {
if (p != t) { // update if slack now >= 2
while ((tail != t || !casTail(t, s)) &&
(t = tail) != null &&
(s = t.next) != null && // advance and retry
(s = s.next) != null && s != t);
}
return p;
}
}
}
tryAppend方法将参数中的节点加入链表尾部,返回参数节点在链表中的前一个节点(如果参数节点作为head节点插入链表,则返回参数节点自身)。
整体流程为:
如果队列中没有节点,参数节点作为head节点。
如果队列中有节点,从tail开始遍历到最后一个节点,如果存在可以与参数节点匹配的unmatched节点,不做处理并返回null(xfer方法调用tryAppend方法发现返回null会重试匹配)。否则将参数节点作为最后一个节点的下一个节点,并更新tail。
在最后的更新逻辑中,可以看到是一个if语句下的循环,并不是每次都会更新tail,将该语句简化以后如下:
if (p != t) {
while (true) {
if(tail == t && casTail(t, s)) {
break;
}
t = tail;
if(t == null) {
break;
}
s = t.next;
if(s == null) {
break;
}
s = s.next;
if(s == null) {
break;
}
if(s == t) {
break;
}
}
}
可以看到在 p == t时是不更新tail的
剩下的场景中
(1)如果tail没有变化,更新tail节点,此时至少插入了两个新节点
(2)如果tail发生了变化,只有在tail节点存在且tail节点后面至少有两个非offlist的节点时更新tail节点。
综上所述,tail节点后至少有两个新节点才会更新tail,而不是每次插入新节点都会更新。
private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
int spins = -1; // initialized after first item and cancel checks
ThreadLocalRandom randomYields = null; // bound if needed
for (;;) {
Object item = s.item;
if (item != e) { // matched //被成功匹配
// assert item != s;
s.forgetContents(); // avoid garbage //s.item = s, s.waiter = null。被成功匹配后最终item属性也是自身
return LinkedTransferQueue.cast(item);
}
if ((w.isInterrupted() || (timed && nanos <= 0)) &&
s.casItem(e, s)) { // cancel //线程被中断或超市后,进行取消
unsplice(pred, s); //节点移除
return e;
}
if (spins < 0) { // establish spins at/near front //初始化自旋次数
if ((spins = spinsFor(pred, s.isData)) > 0)
randomYields = ThreadLocalRandom.current();
}
else if (spins > 0) { // spin //自旋
--spins;
if (randomYields.nextInt(CHAINED_SPINS) == 0)
Thread.yield(); // occasionally yield
}
else if (s.waiter == null) {
s.waiter = w; // request unpark then recheck //waiter赋值,准备下一步的阻塞,waiter属性用于被匹配后唤醒线程
}
else if (timed) { //限时阻塞
nanos = deadline - System.nanoTime();
if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
}
else { //永久阻塞
LockSupport.park(this);
}
}
}
用于线程自旋或阻塞,直到节点被成功匹配或超时或线程中断。
节点被成功匹配,返回匹配的节点的item值。
线程中断或超时,节点取消,移除节点,返回节点自身的item值。
final void unsplice(Node pred, Node s) {
s.forgetContents(); // forget unneeded fields
/*
* See above for rationale. Briefly: if pred still points to
* s, try to unlink s. If s cannot be unlinked, because it is
* trailing node or pred might be unlinked, and neither pred
* nor s are head or offlist, add to sweepVotes, and if enough
* votes have accumulated, sweep.
*/
if (pred != null && pred != s && pred.next == s) { // pred节点存在且不是offlist节点且pred.next == s成立
Node n = s.next;
if (n == null || // s是最后一个节点
(n != s && pred.casNext(s, n) && pred.isMatched())) { // 如果s不是最后一个节点且s不是offlist节点,则通过pred.next = s.next移除s节点。如果pred不是matched节点,return结束。否则还需要后续处理
//尝试更新head节点,从原来的head节点到prev(或s)之间找到第一个unmatched节点作为新的head节点(如果没有找到,则以最后一个matched节点作为head节点)
for (;;) { // check if at, or could be, head
Node h = head;
if (h == pred || h == s || h == null) //pred是head节点 || s是head节点 || head节点不存在(队列中没有节点),return结束
return; // at head or list empty
if (!h.isMatched()) //head节点未匹配,离开循环
break;
Node hn = h.next;
if (hn == null) //head节点是matched节点且head.next不存在,空队列
return; // now empty
if (hn != h && casHead(h, hn)) //更新head节点,head节点
h.forgetNext(); // advance head
}
//sweepVotes计数及处理
if (pred.next != pred && s.next != s) { // recheck if offlist
for (;;) { // sweep now if enough votes
int v = sweepVotes;
if (v < SWEEP_THRESHOLD) {
if (casSweepVotes(v, v + 1)) //增加节点移除的失败次数casSweepVotes
break;
}
else if (casSweepVotes(v, 0)) {//节点移除的失败次数casSweepVotes清0,触发sweep方法进行全局清除
sweep();
break;
}
}
}
}
}
}
unsplice方法用于将节点从链表中移除。
(1)prev节点或者s节点是offlist节点,不会触发head节点更新和sweepVotes计算和处理的逻辑。
(2) 当s节点不是最后一个节点且不是offList节点,通过CAS方式移除s节点,失败则结束方法,不会触发head节点更新和sweepVotes计算和处理的逻辑。
(3)步骤2中通过通过CAS方式移除s节点成功,但prev节点是unmatcher节点,结束方法,不会触发head节点更新和sweepVotes计算和处理的逻辑。
除了上述以外的场景都会进入head节点更新和sweepVotes计算和处理的逻辑。
在head节点更新的逻辑中
(1)如果pred或者s是头结点,renturn结束,对应原来队列中除了s节点以外没有节点(或者都是matched节点)的场景
(2)如果遍历到了最后一个节点,return结束,对应prev和s节点已经不再链表中的场景,否则遍历到prev或s节点就会结束遍历
除了这两种情况外,节点更新逻辑结束之后就会进入sweepVotes计数及处理。
private void sweep() {
for (Node p = head, s, n; p != null && (s = p.next) != null; ) {
if (!s.isMatched()) //unmatched节点,跳过
// Unmatched nodes are never self-linked
p = s;
else if ((n = s.next) == null) // trailing node is pinned
break; //最后一个节点,离开循环
else if (s == n) // stale
// No need to also check for p == s, since that implies s == n
p = head; //offlist节点,从head节点重新开始遍历
else
p.casNext(s, n); //matched节点,移除
}
}
sweep节点进行全局扫描清除,移除matched节点