zoukankan      html  css  js  c++  java
  • java线程基础巩固---多Product多Consumer之间的通讯导致出现程序假死的原因分析

    在上一次中已经实现一个生产者与消费者的初步模型(http://www.cnblogs.com/webor2006/p/8413286.html),但是当时只是一个生产者对应一个消费者,先贴下代码:

    public class ProductConsumerVersion2 {
        private final Object LOCK = new Object();
        private int i = 1;
        /* 此标识用来说明是否当前已经生产过了,默认没有 */
        private volatile boolean isProduced = false;
    
        private void product() {
            synchronized (LOCK) {
                if (isProduced) {
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    i++;
                    System.out.println("P->" + i);
                    LOCK.notify();
                    isProduced = true;
                }
            }
        }
    
        private void comsume() {
            synchronized (LOCK) {
                if (isProduced) {
                    System.out.println("C->" + i);
                    LOCK.notify();
                    isProduced = false;
                } else {
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            ProductConsumerVersion2 productConsumerVersion2 = new ProductConsumerVersion2();
    
            new Thread("P") {
                @Override
                public void run() {
                    while (true)
                        productConsumerVersion2.product();
                }
            }.start();
    
            new Thread("C") {
                @Override
                public void run() {
                    while (true)
                        productConsumerVersion2.comsume();
                }
            }.start();
        }
    
    }

    而实际生产者消费者模型应该是多对多的,所以接下来将其改造一下:

    首先将生产者改成多个,这里采用Java8中的特性来改造,如下:

    接着类似的,将消费者也改造下:

    等于目前已经有了两个生产者与两个消费者,那接下来运行看效果:

    呃~~程序被阻塞住了,那是不是死锁了呢?可以用命令工具来查看确认一下【关于如何查看死锁可以参考:http://www.cnblogs.com/webor2006/p/8320749.html】:

    先用"jps"命令查看当前程序的进程号,如下:

    然后用"jstack"命令查看该进程的线程情况,如下:

    那是为啥会卡住呢?下面增加一些调试语句用来定位跟踪,如下:

    再次编译运行:

    嗯~~整个过程可以清晰的看到了,那接下来则根据输出一一进行详细分析:

    输出:P1 生产了-->2

    第一个生产者来了,当然就直接生产喽,如下:

    【注意】:上面的是对第一个红色的输出的解释,而不是第二个红色的输出哈~

    输出:P1 notify了

    输出:P1 wait了

    接着第一个生产者又来了,由于之前生产的数据还木有被消费掉,那肯定就开始执行此分支了喽:

    输出:P2 wait了

    接着第二个生产者又来生产了,不用说继续wait()
    输出:C1 消费了-->2

    第一个消费者来了,开始消费之前生产的数据2,如下:

    输出:C1 notify了

    接着它去唤醒其它正在wait的线程,如下:

    但是!!目前等待的线程有P1、P2,那唤醒的是哪一个线程呢?下面往下看

    输出:C1 wait了

    接着第一个消费者继续想消费,可此时没数据可消费了,则进入wait状态了,如下:

    输出:P1 生产了-->3

    对于上上个输出中提到了P1、P2都处于wait状态了,那C1这时notify()时唤醒的是哪个消费者呢?答案如输出,此时唤醒的是P1,所以它开始生产了:

    注意:此时P2、C1还是处于wait状态。

    输出:P1 notify了

    接着它通知正处于wait状态的线程:

    由于此时正在wait的线程有:P2、C1,那最终谁被唤醒了呢?继续分析

    输出:P1 wait了

    接着P1继续想生产,由于还没被消费掉,当然此时它又处于wait状态喽:

    输出:P2 wait了

    而上上个notify操作唤醒了这两个正在wait的P2、C1是哪个线程呢?输出也给了答案,很显然是P2,此时它想生产,但是此时还没被消费掉,那它继续wait:

    输出:C2 消费了-->3

    此时第二个消费者C2来了,由于已经有数据可消费了当然直接消费了喽:

    输出:C2 notify了

    消费完了,接着就是通知生产者继续生产,所以:

    但是需要注意:此时wait的线程有C1、P2。那此时唤醒的是哪个线程呢?继续看一面。

    输出:C2 wait了

    接着C2继续想消费,但是很显然已经没有内容可消费了,于是乎开始wait:

    输出:C1 wait了

    而上上个notify发现最终唤醒的是C1线程,而由于目前没内容可消费了,那当然C1也wait了呗,所以:

    最终C1、C2、P2都处理wait状态,P2线程wait了那P1也生产不了,而两个消费者线程也都wait了,进入死循环,于是乎就出现了假死的状态,这完全是程序逻辑所引起的,而非是真正的死锁,因为四个线程都wait等于是放弃了所有CPU的执行权,等着别人唤醒。

    而造成问题的根本就是对于有多个wait的线程,一个notify具体唤醒的是哪个一线程,这个不同的JVM规则是不一样的,有些是按FIFO来唤醒的,有些是随机的,所以这个陷阱需要特别注意。

  • 相关阅读:
    什么?Spring Boot CommandLineRunner 有坑!?
    关于 websocket 跨域的一个奇怪问题…
    电商金额计算的 4 个坑,千万注意了!
    微服务模块划分原则和接口定义原则
    tcp的三次握手(连接)与四次挥手(断开)
    二叉树遍历及算法实现
    elasticsearch搜索 倒排索引
    kubernetes落地-传统web服务迁移
    Docker核心技术-容器管理
    Docker核心技术-镜像管理
  • 原文地址:https://www.cnblogs.com/webor2006/p/8419565.html
Copyright © 2011-2022 走看看