Queue常用类解析之BlockingQueue(五):LinkedTransferQueue

Queue常用类解析之PriorityQueue
Queue常用类解析之ConcurrentLinkedQueue
Queue常用类解析之BlockingQueue(一):PriorityBlockingQueue、DelayQueue和DelayedWorkQueue
Queue常用类解析之BlockingQueue(二):ArrayBlockingQueue
Queue常用类解析之BlockingQueue(三):LinkedBlockingQueue
Queue常用类解析之BlockingQueue(四):SynchronousQueue

接着上文对BlockingQueue的介绍继续向下

八、LinkedTransferQueue

1. 接口TransferQueue

TransferQueue是jdk1.7新增的一个接口,是阻塞队列LinkedBlockingQueue的子接口。
TransferQueue适用于生产者必须等待消费者进行接收数据的阻塞队列场景。
在普通的BlockingQueue中,入队操作和出队操作彼此分离,彼此都只和队列打交道,只需要往队列中存取数据,不需要关心对方的存在。而在TransferQueue中是一种生产者-消费者的应用场景,生产者和消费者之间并不是毫无关系的,生成者生成数据需要等待着一个消费者前来接受,否则就是一次失败的入队操作。因此在TransferQueue的入队和出队操作以外,还有一个更重要的操作概念称为transfer,指生产者将数据传递给消费者的过程。
transfer方法表示生产者传递数据给消费者进行消费,tryTransfer则表示在非阻塞或者限时阻塞的情况下这一过程是否成功。
TransferQueue也可以具有一个固定的容量,事实上SynchronousQueue就是一个典型的容量为0的TransferQueue,虽然其并没有实现TransferQueue接口,在容量为0的TransferQueue中,只有消费者接收了数据才能put成功,因此put操作和transfer操作是同义的。
Queue常用类解析之BlockingQueue(五):LinkedTransferQueue_第1张图片
TransferQueue定义了5个方法。
getWaitingConsumerCount和hasWaitingConsumer都与队列中的等待的消费者有关,前者返回等待的消费者数目,后者返回是否有等待的消费者。
tranfer和两个tryTransfer方法都用于transfer操作,分别是阻塞方法,非阻塞方法和限时阻塞方法。

2. 静态内部类Node

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值,被成功匹配时的匹配逻辑执行完成后为节点自身,取消时为节点自身。

2. LinkedTransferQueue简述

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允许更新的滞后性,存在更新的滞后性,如下图第二张。
head&tai示意图l
head&tai示意图

3. LinkedTransferQueue#xfer(Object, boolean, int, long)

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,匹配不成功加入队列,匹配成功前自旋/阻塞一段时间

4. LinkedTransferQueue#tryAppend(Node, boolean)

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,而不是每次插入新节点都会更新。

5.LinekdTransferQueue#awaitMatch(Node, Node, Object, boolean, long)

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值。

6. LinkedTransferQueue#unsplice(Node, Node)

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计数及处理。

7. LinkedTransferQueue#sweep()

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节点

你可能感兴趣的:(JDK)