分析一个常见的java多线程通信问题(假死现象)

简介: 一件复杂的事,一个人如果不能做,两个人又做的不好,一群人就可能很好的解决了。对于线程来说也是,通过多个线程就能完成一个更复杂的功能,这就需要多个线程协作,协作就需要交流,但是交流总是会出问题的。在这篇文章中我们分析一下java多线程通信过程中出现的一个假死现象。然后给出一个解决办法

一、假死现象重现


为了更好地演示我们的实例,我们使用生产者消费者模式,一边生产一边消费。


下面我们先试着实现一下。这个例子的功能描述如下:

有一个产品a,生产方法生产a,消费方法消费a。然后10个生产线程生产,10个消费线程消费。永不停息。

v2-110d66151309a2660594d029a51d2762_1440w.jpg上面的流程很清晰,一堆线程生产,生产了之后notify消费者去消费。


第一步:定义变量

public class ProduceAndConsumerModel {
    //a表示共享变量
    private int a = 0;
    //lock就是一把锁
    final private Object lock = new Object();
    // isProduced表示是否已经生产的标志
    private volatile boolean isProduced = false;
}


第二步:生产和消费方法(定义在上面的类中)


首先是生产方法

public void produce() throws InterruptedException {
    synchronized (lock) {
        // 如果已经生产了,那就等消费了再生产
        if (isProduced) {
            lock.wait();
        } else {// 没有生产,那就生产一个,并通知消费者去消费
            a++;
            System.out.println("生产者" + Thread.currentThread().getName() 
                                  + "生产一个产品:" + (a));
            lock.notify();
            isProduced = true;
        }
    }
}

然后是消费方法

public void consume() throws InterruptedException {
    synchronized (lock) {
        // 如果有产品,那就消费,并通知生产者可以继续生产了。
        if (isProduced) {
            System.out.println("消费者" + Thread.currentThread().getName() 
                                  + "消费一个产品:" + (a));
            lock.notify();
            isProduced = false;
        } else {// 如果没有产品,那就等待一会
            lock.wait();
        }
    }
}


第三步:测试


public class ProduceAndConsumerModel {
    public static void main(String[] args) {
        ProduceAndConsumerModel model = new ProduceAndConsumerModel();
        // 生产线程一直不停的生产
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    while (true) {
                        model.produce();
                    }
                };
            }.start();
        }
        // 消费线程一直不停的消费
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    while (true) {
                        model.consume();
                    }
                };
            }.start();
        }
    }
}

上面这个例子的功能,在一开始也已经说明了,这里produce和consume方法中,使用的是wait/notify机制来实现的,我们运行一下看会出现什么结果:

v2-3f95a60fd3e683520d165ebc8364bcec_1440w.jpg

我们看到,本来整个程序是永不停歇的,但是在生产了6个产品之后,突然间就停歇了,也就是我们今天的主题,多线程通信出现了假死状态。为什么会出现这种现象呢?我们来分析一下原因。


二、假死状态分析


其实出现这个现象的原因很简单,那就是和我们的wait/notify机制有关,我们几句话来总结一下:


“假死”的现象就是全部线程都进入了WAITING状态(死锁),则程序就不再执行任何业务功能了,整个项目呈停止状态。上面的案例中出现假死的现象是由于仅仅唤醒了同类(生产者唤醒了生产者,消费者唤醒了消费者)的现象大量出现导致的。


下面我们画一张图来分析一下:

v2-b41bf688dd95d1b86cc39a94cdd72de3_1440w.jpg

也就是说notify通知的是是同类。造成了这种堵塞现象。这是其根本原因,而且这张图是我们自己画的。下面我们就直接使用jstack工具来分析一下线程的状态。这两个工具是jdk自带的,我们可以直接使用。


第一步:使用jps查看当前电脑存在的所有java线程

v2-38fb55843aae7931ad62a5b59977cb72_1440w.png

第二步:使用jstack工具查看线程状态信息

v2-3f83e40b69aa4cead10d31ff1e7d0dfd_1440w.jpg

现在我们知道了,目前所有的线程都是出于等待的状态,这也就是假死现象的验证。

假死现象的原因我们知道了,那么我们如何改正呢?


三、假死状态修复


假死现象的改正其实很简单,网上的方式也很多,比如说通过BlockingQueue或者是notifyAll方法。notifyAll方法超级简单,就是把上面produce和consume方法中的notify改成notifyAll方法即可。目的就是通知到所有的其他线程,生产线程该生产的生产,消费线程该消费的消费。


对于java多线程的系列的文章,这算是基础入门。还有更多文章我也经持续发布。感谢支持。OK,今天的文章先到这。

相关文章
|
12月前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
544 1
|
12月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
607 1
|
12月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
421 1
|
12月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
444 2
|
12月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
536 1
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
523 0
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
704 16
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
422 4