Understanding wait, notify and notifyAll using producer-consumer problem

In last post I mentioned about some of the Java Thread’s utility methods. Here I will try to explain wait and notify concept in Threads using classic producer – consumer problem.

Wait: It tells current thread to wait till some notifies it to resume execution, myThread.wait(). There is another version of wait which takes milliseconds as input, wait(2000), it basically tells the thread to wait till someone notifies or a max of 2 seconds.

Notify: Notify statement wakes up one of the threads (randomly chosen in case multiple threads are waiting) waiting on object, to resume execution.

Notifyall: The only difference between notify and notifyAll is that later one notifies all the threads waiting on the object.

Producer- Consumer problem: This is a classic problem where producer and consumer are 2 independent entities. As the name suggests, producer is producing something which is consumed by consumer. The challenge is that one of the entities can be faster than other, for example consumer is consuming faster than producer is producing, in this case the error condition will occur when consumer has nothing to consume (as producer is slow). Vice-versa can be that producer is producing too fast for consumer to consume, so that the storage area is overflowing (as consumer is not able to consume at same speed).

To solve the problem, wait and notify operations come to rescue. So if one entity reaches error state, say consumer has run out of objects to consume, it will wait till a new object is available.

Here is an example of case two where producer is producing faster, so evetually will need to wait at a stage where queue is full till consumer has consumed objects.

The second case (consumer is faster than producer) can be simulated by reversing the sleep time.

package com.kamalmeet;

public class ProducerConsumer {
	public static void main(String s[]) {
		Queue q = new Queue();
		Producer p = new Producer(q);
		Consumer c = new Consumer(q);
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
		System.out.println("Exiting main thread now");
	}
}

class Queue {
	int arr[] = new int[10];
	int index = 0;
	final int MAX = 9;

	public synchronized int remove() {
		if (index == 0) {
			try {
				System.out.println("waiting to remove");
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		index--;
		int num = arr[index];
		notify();
		return num;
	}

	public synchronized void add(int num) {
		if (index == MAX) {
			try {
				System.out.println("waiting to add");
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		notify();
		arr[index] = num;
		index++;
	}
}

class Producer implements Runnable {
	Queue q;
	int num = 0;

	Producer(Queue q) {
		this.q = q;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("adding:" + num);
			q.add(num++);
		}
	}
}

class Consumer implements Runnable {
	Queue q;
	int num = 0;

	Consumer(Queue q) {
		this.q = q;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("removing:" + q.remove());
		}
	}
}