Producer-Consumer Problem using wait() and notifyAll()

Producer Consumer problem is a classic concurrency problem and is often asked in interview. The problem goes as follows: Producer and Consumer are two separate threads which share a same bounded Queue. The role of producer to produce elements and push to the queue. The producer halts producing if the queue is full and resumes producing when the size of queue is not full. The consumer consumes the element from the queue. The consumers halt consuming if the size of queue is 0 (empty) and resumes consuming once the queue has an element.

The problem can be approached using various techniques

How to use wait() and notifyAll() in code

Some of the best practices are listed below

wait() and notifyAll() are the fundamental method of the Object class. Solving this problem, helps understand some of the fundamentals of OOPs and concurrency.

wait() and notifyAll() is shared on the same object between two threads. In this case it will be the Queue.

wait() and notifyAll() should be used inside the synchronized block.

The methods should be called inside the for loop. Since wait is called inside a conditional block e.g. producer thread should call wait() if queue is full, first instinct goes towards using if block, but calling wait() inside if block can lead to subtle bugs because it's possible for thread to wake up spuriously even when waiting condition is not changed.

The Producer start producing objects and pushing it to the Queue. Once the queue is full, the producer will wait until consumer consumes it and it will start producing again. Similar behavior is displayed by consumer. where the consumer waits until there is a single element in queue. It will resume consumer once the queue has element.

public class ProducerConsumer {

  static Queue<Integer> queue = new LinkedList();
  static int MAX_SIZE = 5;

  public static void main(String[] args) {

    Producer producer = new Producer();
    Consumer consumer = new Consumer();
    producer.start();
    consumer.start();
  }

  static class Producer extends Thread {
    Random random = new Random();

    public void run() {
      while (true) {
        synchronized (queue) {
          while (queue.size() == MAX_SIZE) {
            System.out.println("Waiting for consumer to consume.");
            try {
              queue.wait();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
          int i = random.nextInt(MAX_SIZE);
          System.out.println("Producer " + i);
          queue.add(i);
          queue.notifyAll();
        }
      }
    }
  }

  static class Consumer extends Thread {
    public void run() {
      while (true) {
        synchronized (queue) {
          while (queue.size() == 0) {
            System.out.println("Waiting for producer to produce.");
            try {
              queue.wait();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }

          int element = queue.poll();
          System.out.println("Consumed: " + element);
          queue.notifyAll();
        }
      }
    }
  }
}
//Output
Waiting for producer to produce.
Producer 4
Producer 1
Producer 2
Consumed: 4
Consumed: 1
Consumed: 2
Waiting for producer to produce.
Producer 4
Consumed: 4
Waiting for producer to produce.
Producer 3
Consumed: 3
Waiting for producer to produce.
Producer 3
Consumed: 3
Waiting for producer to produce.
Producer 4
Producer 2
Consumed: 4
Consumed: 2