高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析

20191031000606569.png

概述

高并发编程-线程通信_使用wait和notify进行线程间的通信 - 遗留问题


20191001005010259.png


我们看到了 应用卡住了 。。。。 怀疑是不是死锁呢? (其实没有)


jstack或者可视化工具检测是否死锁(没有)

C:\Users\Mr.Yang>E:
E:\>cd E:\Program Files\Java\jdk1.8.0_161\bin
E:\Program Files\Java\jdk1.8.0_161\bin>jps
1504 MultiProduceConsumerDemo
E:\Program Files\Java\jdk1.8.0_161\bin>jstack 1504
2019-10-01 00:44:23
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode):
"DestroyJavaVM" #15 prio=5 os_prio=0 tid=0x0000000002983800 nid=0x3348 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Thread-3" #14 prio=5 os_prio=0 tid=0x0000000019fa5000 nid=0x3af0 in Object.wait() [0x000000001abff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078b682768> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.artisan.test.MultiProduceConsumerDemo.consume(MultiProduceConsumerDemo.java:42)
        - locked <0x000000078b682768> (a java.lang.Object)
        at com.artisan.test.MultiProduceConsumerDemo$2.run(MultiProduceConsumerDemo.java:63)
"Thread-2" #13 prio=5 os_prio=0 tid=0x0000000019fa4000 nid=0x9e4 in Object.wait() [0x000000001aaff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078b682768> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.artisan.test.MultiProduceConsumerDemo.consume(MultiProduceConsumerDemo.java:42)
        - locked <0x000000078b682768> (a java.lang.Object)
        at com.artisan.test.MultiProduceConsumerDemo$2.run(MultiProduceConsumerDemo.java:63)
"Thread-1" #12 prio=5 os_prio=0 tid=0x0000000019fa0000 nid=0x37cc in Object.wait() [0x000000001a9ff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078b682768> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.artisan.test.MultiProduceConsumerDemo.produce(MultiProduceConsumerDemo.java:20)
        - locked <0x000000078b682768> (a java.lang.Object)
        at com.artisan.test.MultiProduceConsumerDemo$1.run(MultiProduceConsumerDemo.java:55)
"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000019f9f800 nid=0x1bd4 in Object.wait() [0x000000001a8fe000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078b682768> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.artisan.test.MultiProduceConsumerDemo.produce(MultiProduceConsumerDemo.java:20)
        - locked <0x000000078b682768> (a java.lang.Object)
        at com.artisan.test.MultiProduceConsumerDemo$1.run(MultiProduceConsumerDemo.java:55)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000000019d2f800 nid=0x3ba4 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000000019ca3800 nid=0x37e4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000019c54800 nid=0x2db0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000019c50800 nid=0x37bc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000019c2e000 nid=0x2ed4 runnable [0x000000001a2fe000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x000000078b5dad50> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x000000078b5dad50> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000018860000 nid=0x34f8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019c08800 nid=0x1514 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001883a000 nid=0x22c in Object.wait() [0x0000000019b9e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078b408ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x000000078b408ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000002a73800 nid=0x29bc in Object.wait() [0x0000000019a9f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000078b406b68> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000078b406b68> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x0000000018818000 nid=0x381c runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002998000 nid=0x3024 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000299a000 nid=0x229c runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000299b800 nid=0x2bbc runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000299d000 nid=0x255c runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000019d67800 nid=0x1c5c waiting on condition
JNI global references: 334
E:\Program Files\Java\jdk1.8.0_161\bin>


可以看到 并没有死锁的发生

或者 使用 jvisualvm 、 jmc 工具来看下都行

(jmc截图)

20191001005618588.png


并且可以看到4个线程 均是 WAITING 状态

(jmc截图)

20191001011416610.png


原因分析

20191001080629528.png


为了方便观察,我们改造下,给线程起个名

package com.artisan.test;
import java.util.stream.Stream;
public class MultiProduceConsumerDemo {
    // 对象监视器-锁
    private final Object LOCK = new Object();
    // 是否生产出数据的标识
    private boolean isProduced = false;
    // volatile 确保可见性, 假设 i 就是生产者生产的数据
    private volatile int i = 0 ;
    public  void produce(){
        synchronized (LOCK){
            System.out.println(Thread.currentThread() + " GOT LOCK  " + isProduced);
            String msg = isProduced ? "已生产货物" : "没有货物可搬运";
            if (isProduced){
                try {
                    System.out.println(Thread.currentThread() + " wait becauseof  " + msg );
                    LOCK.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                i++;
                System.out.println(Thread.currentThread() + " Produce:" + i);
                LOCK.notify();
                isProduced = true;
            }
        }
    }
    public void consume(){
        // 加锁
        synchronized (LOCK){
            System.out.println(Thread.currentThread() + " GOT LOCK " + isProduced);
            String msg = isProduced ? "已生产货物" : "没有货物可搬运";
            if (isProduced){
                System.out.println(Thread.currentThread() + " Consume:" + i);
                LOCK.notify();
                isProduced = false;
            }else{
                try {
                    System.out.println(Thread.currentThread() + " wait becauseof  " + msg);
                    LOCK.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        MultiProduceConsumerDemo produceConsumerDemo = new MultiProduceConsumerDemo();
        Stream.of("P1","P2").forEach(n-> new Thread(n){
            @Override
            public void run() {
                while(true) produceConsumerDemo.produce();
            }
        }.start());
        Stream.of("C1","C2").forEach(n->new Thread(n){
            @Override
            public void run() {
                while(true) produceConsumerDemo.consume();
            }
        }.start());
    }
}


运行下 ,可以看到程序并没有持续运行,而是假死了…

(每次运行的结果都有可能不一样,这里我们以这次的运行结果来分析下)

2019100113383794.png


逐步分析下:

生产者的代码2019100720370841.png

消费者代码


20191007212957913.png


  1. 线程P1锁,没有货物生产,isProduce=false
  2. 线程P1,生产货物 ,紧接着 LOCK.notify(); isProduced = true; ,其实第一步的LOCK.notify() 是没有什么作用的,因为没有任何线程wait. 执行完以后释放锁。 对应日志

20191007211804997.png

  1. 紧接着,P1又抢到了锁,但是生产后没有被消费,所以直接进入LOCK.wati. 执行完以后释放锁。P1-----WAITING . 对应日志
  2. 20191007211823248.png
  3. P2同上一步P1的操作 ,P2-----WAITING 对应日志


20191007212839387.png

C1 抢到资源 ,isProduce已经生产,所以C1线程直接消费,消费完成以后要通知生产者继续生产,即唤醒消费者 (LOCK.notify();),将isProduce标志位置为false . 执行完以后,释放锁

20191007212933500.png

  1. C1-----WAITING日志如下
  2. 紧接着C1又抢到了锁,因没有生产,所以进入LOCK.wait() ,释放锁。 对应日志

20191007213159525.png


依次类推… 直到最后C2 唤醒了C1 ,此时C1看到isProduce=false, 则C1进入了wait ,这个时候4个线程都是watiing的状态了,就出现了4个线程均是wait状态,都不执行了,出现了假死 (因为notify方法,唤醒一个线程,具体是哪个线程是不确定的。)

那如何解决呢? 下篇博文我们一起来探讨下


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
21天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3月前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
45 1
[Java]线程生命周期与线程通信
|
2月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
46 3
|
3月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
32 1
|
8月前
|
消息中间件 Java Linux
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜
|
7月前
|
缓存 NoSQL Java
Java高并发实战:利用线程池和Redis实现高效数据入库
Java高并发实战:利用线程池和Redis实现高效数据入库
557 0
|
5月前
|
监控 算法 Java
企业应用面临高并发等挑战,优化Java后台系统性能至关重要
随着互联网技术的发展,企业应用面临高并发等挑战,优化Java后台系统性能至关重要。本文提供三大技巧:1)优化JVM,如选用合适版本(如OpenJDK 11)、调整参数(如使用G1垃圾收集器)及监控性能;2)优化代码与算法,减少对象创建、合理使用集合及采用高效算法(如快速排序);3)数据库优化,包括索引、查询及分页策略改进,全面提升系统效能。
64 0
|
7月前
|
存储 NoSQL Java
探索Java分布式锁:在高并发环境下的同步访问实现与优化
【6月更文挑战第30天】Java分布式锁在高并发下确保数据一致性,通过Redis的SETNX、ZooKeeper的临时节点、数据库操作等方式实现。优化策略包括锁超时重试、续期、公平性及性能提升,关键在于平衡同步与效率,适应大规模分布式系统的需求。
210 1
|
6月前
|
算法 Java 调度
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
|
6月前
|
监控 网络协议 Java
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
94 0

热门文章

最新文章