Search code examples
javamultithreadingdeadlockproducer-consumer

Java multithreaded program (producer and consumer) hangs..?


I have a simple variant of the producer and consumer example in 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.


Solution

  • As mentioned above in the comments, checking a condition in a while loop is really dangerous. 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. 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.

    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. Then the Consumer compares count which is 0 for him and stucks in the loop. 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. 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.

    P.S. I am not sure if you make count as AtomicInteger you will prevent the deadlock, but at least you can try that approach. But anyway, prevent checking conditions with while loop in multithreading.