Search code examples
javamultithreadingthreadpoolwaitproducer-consumer

Producer Consumer problem in java thread is waiting very long


I have written my own implementation of producer consumer problem. But my thread is waiting very long and even with notify. I don't get what is happening.

MainClass.java

public class MainClass {
    public static void main(String[] args) throws InterruptedException {
        final Thread produce = new Thread(new Runnable() {
            @Override
            public void run() {
                    synchronized (Shop.LL) {
                        if (null != Shop.LL && Shop.LL.size() == 0) {
                            try {
                                Producer.produce();
                                Shop.LL.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            notify();
                    }}}});

        final Thread consume = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Shop.LL) {
                    System.out.println("Entering consumer");
                    try {
                        Consumer.buy();
                        Shop.LL.wait();
                    } catch (InterruptedException e) {e.printStackTrace();}
                notify();

                }}});

        produce.start();
        consume.start();
        produce.join();
        consume.join();
    }}

Producer.java

public class Producer {
    private static String[] fruits = { "apple", "orange", "pineapple", "banana", "cherry", "kiwi" };
    protected static synchronized void produce() {
        if (Shop.LL.size() == 0) {
            System.out.println("Producer selling the .....");
            do {
                System.out.println(fruits[Shop.i]);
                Shop.LL.add(fruits[Shop.i]);
                Shop.i++;
            } while (Shop.LL.size() <= 1);
        }}}

Consumer.java

 public class Consumer {
        protected static synchronized void buy() {
            int i = 0;
            if (Shop.LL.size() > 0) {
                System.out.println("Cosumer buying the .....");
                while (i < 2) {
                    System.out.println(Shop.LL.get(i));
                    i++;
                }}
            Shop.LL.clear();
        }}

Shop.java

import java.util.LinkedList;
import java.util.List;
public class Shop {
    protected static List<String> LL = new LinkedList<String>();
    protected static int i = 0;
}

OUTPUT:

Entering producer 
Producer selling the ..... apple orange 
Entering consumer 
Cosumer buying the ..... apple orange

Here after this both threads are waiting indefinitely and don't know why. But what is the cause?

Thread Dump

8976:
2023-03-24 13:43:31
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):

"Thread-1" #11 prio=5 os_prio=0 tid=0x000000001869b000 nid=0x2ca8 in Object.wait() [0x0000000018fae000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d5f4ee28> (a java.util.LinkedList)
    at java.lang.Object.wait(Unknown Source)
    at com.consumer.producer.MainClass$2.run(MainClass.java:58)
    - locked <0x00000000d5f4ee28> (a java.util.LinkedList)
    at java.lang.Thread.run(Unknown Source)

"Thread-0" #10 prio=5 os_prio=0 tid=0x0000000018694000 nid=0x1328 in Object.wait() [0x0000000018eaf000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d5f4ee28> (a java.util.LinkedList)
    at java.lang.Object.wait(Unknown Source)
    at com.consumer.producer.MainClass$1.run(MainClass.java:25)
    - locked <0x00000000d5f4ee28> (a java.util.LinkedList)
    at java.lang.Thread.run(Unknown Source)

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

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

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

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

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000016e4b800 nid=0x2de0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000016e4a000 nid=0xe38 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000000230b800 nid=0x2688 in Object.wait() [0x00000000181ae000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d5e08ed0> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(Unknown Source)
    - locked <0x00000000d5e08ed0> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(Unknown Source)
    at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000016e19000 nid=0x2b4c in Object.wait() [0x00000000180ae000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d5e06bf8> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Unknown Source)
    at java.lang.ref.Reference.tryHandlePending(Unknown Source)
    - locked <0x00000000d5e06bf8> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)

"main" #1 prio=5 os_prio=0 tid=0x00000000020be800 nid=0x1908 in Object.wait() [0x000000000220f000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d5e869f0> (a java.lang.Thread)
    at java.lang.Thread.join(Unknown Source)
    - locked <0x00000000d5e869f0> (a java.lang.Thread)
    at java.lang.Thread.join(Unknown Source)
    at com.consumer.producer.MainClass.main(MainClass.java:79)

"VM Thread" os_prio=2 tid=0x0000000016e17800 nid=0x3af8 runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002228000 nid=0x2e60 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002229800 nid=0x16c4 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000222b000 nid=0xe9c runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000222d800 nid=0x22e4 runnable 

"VM Periodic Task Thread" os_prio=2 tid=0x00000000185ba000 nid=0x21f0 waiting on condition 

JNI global references: 4

Solution

  • I have modified OP code and tried to keep everything same.
    Solution 1: Single producer and single consumer thread

    • Producer: if Shop.LL is full, then wait on Shop.LL and do not produce anything else produce items and add to Shop.LL.
    • Consumer: if Shop.LL is empty, then wait and do not consume anything else consume and remove item from Shop.LL.
    
    import java.util.LinkedList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class ClientClass {
        // moved items from the producer class, just to use its size in consumer class, but change does not have any impact on the issue
        private static final String[] fruits = {"apple", "orange", "pineapple", "banana", "cherry", "kiwi"};
    
        // respective trackers to check the conditions
        protected static volatile AtomicInteger fruitProducerTracker = new AtomicInteger();
        protected static volatile AtomicInteger fruitConsumerTracker = new AtomicInteger();
    
        public static void main(String[] args) throws InterruptedException {
            final Thread produce = new Thread(() -> {
                synchronized (Shop.LL) {
                    // wait before producing 
                    if (Shop.LL.size() != 0) {
                        try {
                            Shop.LL.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    try {
                        Producer.produce();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Shop.LL.notifyAll();
                }
            });
    
            final Thread consume = new Thread(() -> {
                synchronized (Shop.LL) {
                    System.out.println("Entering consumer");
                    if (Shop.LL.size() == 0) {
                        try {
                            Shop.LL.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    Consumer.buy();
                    Shop.LL.notifyAll();
                }
            });
    
            consume.start();
            produce.start();
    
            consume.join();
            produce.join();
        }
    
        static class Producer {
            protected static synchronized void produce() throws InterruptedException {
                if (Shop.LL.size() == 0) {
                    // To publish all the items, adding all items from the same thread
                    for (int j = 0; j < fruits.length; j++) {
                        final String fruit = fruits[fruitProducerTracker.getAndIncrement()];
                        Shop.LL.add(fruit);
                        System.out.println(Thread.currentThread().getName() + " : Producer selling the ....." + fruit);
                    }
                }
            }
        }
    
        static class Consumer {
            protected static synchronized void buy() {
                // To consume all items need to use while, if we use if condition similar to OP, then it will consume only 1 item.
                while (fruitConsumerTracker.get() < fruits.length) {
                    final String item = Shop.LL.remove(0);
                    fruitConsumerTracker.incrementAndGet();
                    System.out.println(Thread.currentThread().getName() + " : Consumer buying the ....." + item);
                }
            }
        }
    
        static class Shop {
            static final List<String> LL = new LinkedList<String>();
        }
    }
    /*
    Entering consumer
    Thread-0 : Producer selling the .....apple
    Thread-0 : Producer selling the .....orange
    Thread-0 : Producer selling the .....pineapple
    Thread-0 : Producer selling the .....banana
    Thread-0 : Producer selling the .....cherry
    Thread-0 : Producer selling the .....kiwi
    Thread-1 : Consumer buying the .....apple
    Thread-1 : Consumer buying the .....orange
    Thread-1 : Consumer buying the .....pineapple
    Thread-1 : Consumer buying the .....banana
    Thread-1 : Consumer buying the .....cherry
    Thread-1 : Consumer buying the .....kiwi
    */