码迷,mamicode.com
首页 > 其他好文 > 详细

利用jvisualvm.exe搞一个关于生产者消费者的另一些纠结的问题

时间:2020-07-09 00:39:49      阅读:86      评论:0      收藏:0      [点我收藏+]

标签: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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!