Search code examples
javamultithreadingsynchronizationwaitnotify

Thread behaviour on member variable lock


When running the below code, it throws IllegalMonitorStateException.

class Consumer {
    private int capacity = 5;
    private Queue<Integer> queue = new PriorityQueue<Integer>(capacity);

    class ConsumerWaitNotify implements Runnable {
        public void run() {
            try {
                consumeItem();
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();

            }
        }
        void consumeItem() {
            try {
                synchronized (queue) {            //Line 1
                    while(queue.size() == 0) {
                        System.out.format("%n%s: Waiting..Empty Queue, Size: %d%n", Thread.currentThread().getName(),
                                            queue.size());
                        wait();           //Line 2
                    }
                    int popItem = queue.poll();
                    System.out.format("%n%s: Consumes Item: %d, Size: %d", Thread.currentThread().getName(), 
                                        popItem, queue.size());
                    notify();
                }           
            } catch(InterruptedException e) {
                e.printStackTrace();

            }
        }
    }
}

public class ConsWaitNotify {

    public static void main(String[] args) {
        Consumer pc = new Consumer();
        Consumer.ConsumerWaitNotify cwn = pc.new ConsumerWaitNotify();
        Thread consumer = new Thread(cwn, "CONSUMER");
        consumer.start();

    }
}

Below is the ERROR:

CONSUMER: Waiting..Empty Queue, Size: 0
Exception in thread "CONSUMER" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:485)
    at com.threadconcurrency.lock.prodcons.Consumer$ConsumerWaitNotify.consumeItem(ConsWaitNotify.java:67)
    at com.threadconcurrency.lock.prodcons.Consumer$ConsumerWaitNotify.run(ConsWaitNotify.java:52)
    at java.lang.Thread.run(Thread.java:619)

While debugging I found that, when line 2 i.e. wait() command is executed then thread instead going out of runnable state, it jumps to Line 1 for execution, and it executes it two times. Hence it throws the exception.

What I assume is that after wait, thread may have release the lock of object (queue) but still holds the object of class ConsumerWaitNotify, and that's why it behaves like that.

I have achieved what I want by making a separate class of Consumer with method consumeItem() having synchronised(this) code and ConsumerWaitNotify with Consumer object as member.

But what's wrong in this. I am still confused and not able to predict the exact behavior. Can anyone help me out?


Solution

  • You are synchronizing on variable queue but call wait() and notify() on the this object. You either need to hold the lock with synchornized(this) or call queue.wait() and queue.notify() to ensure that you notify the same monitor you have the lock for. You can take a look at Guarded Blocks docs.

    Do note that you don't need to implement the queue yourself. JDK provides a few java.util.concurrent.BlockingQueue implementations:

    A Queue that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.