Search code examples
javamultithreadingproducer-consumer

Producer Consumer Misunderstanding in Threading


I want the ProducerThread to produce random values upto 10 and then expect ConsumerThread to consumer those values of Queue. Somewhere Producer is generating adding the values more than once. I have a concept that when we call notify on an object than that Thread would release lock and give chance to Thread which was expecting updation.

Here is the code, please correct my understanding.

public class ProducerThread extends Thread {

    Queue<Integer> values;

    ProducerThread(Queue<Integer> values) {
        this.values = values;
    }

    public void run() {
        while(true) {
            synchronized(values) {
                double totalValues = Math.random()*10;
                System.out.println("Going to populate total values:" + totalValues);

                for (int i = 1; i <= totalValues; i++) {
                    values.add(i);
                    System.out.println("Value updated: " + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                values.notify();
            }
        }
    }

}



public class ConsumerThread extends Thread {
    Queue<Integer> values;

    ConsumerThread(Queue<Integer> values) {
        this.values = values;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (values) {

                try {
                    // Consumer Thread waits until values are populated by Producer Thread
                    if(values.isEmpty()) {
                        values.wait();
                    }

                    Iterator<Integer> iterateValues = values.iterator();
                    System.out.println("Going to consume values: " + values.size());
                    while (iterateValues.hasNext()) {
                        Integer removedValue = iterateValues.next();
                        System.out.println("Value deleted: " + removedValue);
                    }
                    values.clear();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}


public class Test {
    public static void main(String[] args) {
        Queue<Integer> values = new LinkedList<Integer>();
        ProducerThread producer = new ProducerThread(values);
        ConsumerThread consumer = new ConsumerThread(values);

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

    }
}

Solution

  • Aha! You have encountered the dreaded race condition!

    Immediately after notify returns in your ProducerThread, said thread still has the lock. The ConsumerThread, woken up by the notify, will see that the lock is not available, and will wait until it becomes available.

    Then the ProducerThread gives up the lock, it will then enter a race with the ConsumerThread to take that lock back (ProducerThread by way of re-entering the synchronized block, and ConsumerThread by means of having to return from wait). There is no guarantee which of these will win.

    If you want your ProducerThread to wait for the items to be consumed before producing more, you should consider another wait/notify for that scenario.

    EDIT: This image might help to explain things a bit more clearly. Wait Notify Diagram