简体   繁体   English

Java 多线程程序(生产者和消费者)挂起..?

[英]Java multithreaded program (producer and consumer) hangs..?

I have a simple variant of the producer and consumer example in Java.我有一个 Java 中生产者和消费者示例的简单变体。 I think it should work ok but it hangs.我认为它应该可以正常工作,但它挂起。 I first thought it might be some form of deadlock, but when I look at the thread dump, it seems it's stuck in the middle of a infinite loop.我一开始以为可能是某种形式的死锁,但是当我查看线程转储时,它似乎卡在了无限循环的中间。 Anyhow, I don't understand why the program behaves so strange and want to know a possible scenario that could have caused the hang.无论如何,我不明白为什么程序表现得如此奇怪,想知道可能导致挂起的情况。


public class ProducerConsumer { 

    public static void main(String[] args) {
        Buffer buffer = new Buffer(7);  
        Producer producer = new Producer(buffer);
        Consumer consumer = new Consumer(buffer);

        producer.start();
        consumer.start();
    }
}

class Buffer{ 
    private char [] buffer;
    private int count = 0, in = 0, out = 0;

    private final Object object1 = new Object(); 

    Buffer(int size){
        buffer = new char[size];
    }

    public void put (char c) {
        while(count == buffer.length) ;

        synchronized(object1) { 
            System.out.println("Producing" + c + "...");
            buffer[in] = c;
            in = (in + 1) % buffer.length;
            count++;    
        }
    }

    public char get() {
        while(count == 0) ;  
        char c = buffer[out];

        synchronized(object1) {
            out = (out + 1) % buffer.length;
            count--;
            System.out.println("Consuming" + c + "...");
            }
        return c;
    }
}

class Producer extends Thread {
    private Buffer buffer;

    Producer(Buffer b){
        buffer = b;
    }

    public void run() {
        for(int i = 0; i < 10; i++) {
            buffer.put((char) ('A' + i%26 ));
        }
    }
}

class Consumer extends Thread {
    private Buffer buffer;

    Consumer(Buffer b){
        buffer = b;
    }

    public void run() {
        for(int i = 0; i < 10; i++) {
            buffer.get();
        }
    }
}

And here's the program output and its thread dump.这是程序输出及其线程转储。

c:\Temp\tt>java ProducerConsumer
ProducingA...
ConsumingA...
ProducingB...
ConsumingB...
ProducingC...
ConsumingC...
ProducingD...
ConsumingD...
ProducingE...
ConsumingE...
ProducingF...
ConsumingF...
ProducingG...
ConsumingG...
ProducingH...
ConsumingH...
ProducingI...
ProducingJ...
2019-12-08 22:28:51
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002761800 nid=0x3990 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #12 prio=5 os_prio=0 tid=0x000000003a12a800 nid=0x36f0 runnable [0x000000003b04f000]
   java.lang.Thread.State: RUNNABLE
        at Buffer.get(ProducerConsumer.java:36)
        at Consumer.run(ProducerConsumer.java:71)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000003a0ff000 nid=0x30a4 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x000000003a05a000 nid=0x3890 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000003a057000 nid=0x16a4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000003a055000 nid=0x4b4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000003a04d800 nid=0x4160 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000039ff9000 nid=0x1028 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000039ff7800 nid=0x4f80 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000039fe1800 nid=0x21c in Object.wait() [0x000000003a5be000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000066c408ed8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x000000066c408ed8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000039fe0800 nid=0x4588 in Object.wait() [0x000000003a4bf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000066c406c00> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000066c406c00> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x00000000381e9800 nid=0x5418 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002776800 nid=0x2580 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002778000 nid=0x4c98 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000277a000 nid=0x560c runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000277b800 nid=0x3e78 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000277d800 nid=0x1f24 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002780000 nid=0x5074 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002783000 nid=0x3d88 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002784000 nid=0x4d18 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000003a11c800 nid=0x3f8c waiting on condition

JNI global references: 5

Heap
 PSYoungGen      total 304640K, used 31335K [0x000000066c400000, 0x0000000681800000, 0x00000007c0000000)
  eden space 261120K, 12% used [0x000000066c400000,0x000000066e299c00,0x000000067c300000)
  from space 43520K, 0% used [0x000000067ed80000,0x000000067ed80000,0x0000000681800000)
  to   space 43520K, 0% used [0x000000067c300000,0x000000067c300000,0x000000067ed80000)
 ParOldGen       total 696320K, used 0K [0x00000003c4c00000, 0x00000003ef400000, 0x000000066c400000)
  object space 696320K, 0% used [0x00000003c4c00000,0x00000003c4c00000,0x00000003ef400000)
 Metaspace       used 2642K, capacity 4492K, committed 4864K, reserved 1056768K
  class space    used 283K, capacity 388K, committed 512K, reserved 1048576K


Any hints will be appreciated.任何提示将不胜感激。

The above situation will arise when your consumer Thread starts consuming before the Producer Thread.当您的消费者线程在生产者线程之前开始消费时,就会出现上述情况。 You can simulate it bu putting some sleep in between.您可以模拟它,但在两者之间放置一些睡眠。

Buffer buffer = new Buffer(7);
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);

consumer.start();
Thread.sleep(2000);
producer.start();

Now to fix it you can use wait and notify as suggested by everyone.现在要修复它,您可以按照每个人的建议使用等待和通知。

I have updated the Buffer code.我已经更新了缓冲区代码。 Please check if this works for you.请检查这是否适合您。

class Buffer{
        private char [] buffer;
        private int count = 0, in = 0, out = 0;

        private final Object object1 = new Object();

        Buffer(int size){
            buffer = new char[size];
        }

        public void put (char c) throws InterruptedException {
            //while(count == buffer.length) ;

            synchronized(object1) {
                if(count == buffer.length){
                    object1.wait();
                }
                System.out.println("Producing" + c + "...");
                buffer[in] = c;
                in = (in + 1) % buffer.length;
                count++;
                object1.notify();
            }
        }

        public char get() throws InterruptedException {
            //while(count == 0) ;

            while (true){
                synchronized(object1) {
                    if(count == 0){
                        object1.wait();
                    }
                    char c = buffer[out];
                    out = (out + 1) % buffer.length;
                    count--;
                    System.out.println("Consuming" + c + "...");
                    object1.notify();
                    return c;
                }

            }

        }
    }

As mentioned above in the comments, checking a condition in a while loop is really dangerous.正如上面评论中提到的,在 while 循环中检查条件真的很危险。 One of the solutions is to have two conditions Empty and Full, and when Consumer checks that the count is 0 it waits on the Empty, and if Producer check that the count approached the limit it waits on Full.解决方案之一是有两个条件 Empty 和 Full,当 Consumer 检查计数为 0 时,它等待 Empty,如果 Producer 检查计数接近限制,则它等待 Full。 Then respectively Consumer awakes the Producer when the count is less than the limit and Producer awakes the Consumer when the count is not 0.然后分别在计数小于限制时Consumer唤醒Producer,当计数不为0时Producer唤醒Consumer。

To answer your question, how the deadlock happens:为了回答你的问题,死锁是如何发生的:

The Consumer thread takes the value of count variable and wants to compare but before it compares the Producer thread manages to fill the buffer and get into the while loop.消费者线程获取 count 变量的值并想要进行比较,但在比较之前,生产者线程设法填充缓冲区并进入 while 循环。 Then the Consumer compares count which is 0 for him and stucks in the loop.然后消费者比较他的计数为 0 并卡在循环中。 It becomes that both threads are waiting as for Consumer the count is 0 and for Producer, the count is the size of the buffer.两个线程都在等待,因为消费者的计数为 0,而生产者的计数是缓冲区的大小。 My advice when synchronizing is to count each possible case and firstly to understand which operations you can consider as atomic, which means that two different threads cannot do that simultaneously.我在同步时的建议是计算每种可能的情况,首先了解您可以将哪些操作视为原子操作,这意味着两个不同的线程不能同时执行。

I hope my shared knowledge will help you.希望我分享的知识能帮到你。

PS I am not sure if you make count as AtomicInteger you will prevent the deadlock, but at least you can try that approach. PS 我不确定你是否将算作 AtomicInteger 你会防止死锁,但至少你可以尝试这种方法。 But anyway, prevent checking conditions with while loop in multithreading.但无论如何,请防止在多线程中使用 while 循环检查条件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM