标签:blank 制造 卡住了 元素 ali final 消费 跳过 两种
在利用jvisualvm.exe搞一个关于生产者消费者的一个纠结的问题,我们已经看到如何在生产者消费者模型中,由于队列的不安全导致消费者一直空转的情况,已经如何解决该问题。接下来我们继续跟踪该问题的其他几种并发情况,现在先把代码中的关键那一行休眠注释掉,还是用LinkedList作为队列跑一下,结果又让我们大跌眼镜了:
是的,看起来很完美的日志,生产者也生产了,消费者也消费了,最后一个元素都出队入队了,但依然卡住了。祭出jvisualvm,消费者线程还是在空转:
这次队列出现了莫名其妙的两个null对象,其他对象被正常的生产出来又消费掉了。我们还得看看LinkedList的源码:
/** * Pointer to first node. * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; /** * Pointer to last node. * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last;
注释告诉我们第一个节点和最后一个节点只有两种情况:要么同时为null,要么本身不为空,但它的前一个(对第一个节点来说)或下一个(对最后一个节点来说)是null。怎么理解呢,这就得从LinkedList本身说起了:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
我们看到它实现了Deque接口,它是Queue的子类,支持双向遍历,所以LinkedList是一个双向链表,既可以从first节点出发向后遍历,又可以从last节点向前遍历。就像一条双头蛇,两边都是头,两边都是尾。而且它支持null对象,所以就出现上面的情况。一图抵千言,把head和tail换成first和last,一样的说法:
多跑几次,也许你还会碰到这种情况,开始生产者消费者步调是一致的:
然而到了后来,只有生产者一个人在玩,消费者又被骗去默默死循环了:
看看队列的情况:
嗯,前面消费者正常的消费掉了一大部分,生产者的生产速度跟不上,队首first变成了null,所以它没得办法,只能去死循环,因为如下两段代码决定了一旦队列为空,就会再次进入消费者的拉取循环中:
/** * Retrieves and removes the head (first element) of this list. * * @return the head of this list, or {@code null} if this list is empty * @since 1.5 */ public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); }
// 如果拉取到的对象是null,跳过继续拉取 if (element == null) { continue; }
虽然后来生产者继续补充产品,但如果它无法告知消费者,那么也将无奈的于事无补了,因为消费者已经没法享用。只能把这1260个数字(除掉队首的null)永远的留在队列中。由此我们知道,只要某一时刻消费者赶上了生产的速度,队列空了,那么消费者就得去死循环。
我们算下,队列在8738时步调一致,下一刻消费者跑去死循环,生产者继续工作,往队列中投入9999-8739=1260个数字。跟上面的堆内存中的队列个数可以对上。唯一一种正常结束的情况是:生产者早于消费者生产,至少同时生产,接下来某个时间要快于消费者的消费速度,最终消费者最后消费完,结束线程。换句话说,生产者的速度必须必消费者快,一旦出现消费者取到队首为null的情况,就可能陷入泥潭不可自拔。
最后我们手动给消费制造一点延时,让它慢一点:
我们只需要给消费类加一点点的休眠时间,生产者就能先完成任务,消费者随后也完成了消费。
利用jvisualvm.exe搞一个关于生产者消费者的另一些纠结的问题
标签:blank 制造 卡住了 元素 ali final 消费 跳过 两种
原文地址:https://www.cnblogs.com/wuxun1997/p/13270056.html