Sunday, December 30, 2012

Producer Consumer Problem with Wait and Notify Example

Producer Consumer Problem is a classical concurrency problem and in fact it is one of the concurrency design pattern. In last article we have seen solving Producer Consumer problem in Java using blocking Queue but one of my reader emailed me and requested code example and explanation of solving Producer Consumer problem in Java  with wait and notify method as well, Since its often asked as one of the top coding question in Java. In this Java tutorial, I have put the code example of wait notify version of earlier producer consumer concurrency design pattern. You can see this is much longer code with explicit handling blocking conditions like when shared queue is full and when queue is empty. Since we have replaced BlockingQueue with Vector we need to implement blocking using wait and notify and that's why we have introduced produce(int i) and consume() method. If you see I have kept consumer thread little slow by allowing it to sleep for 50 Milli second to give an opportunity to producer to fill the queue, which helps to understand that Producer thread is also waiting when Queue is full.

Java program to solve Producer Consumer Problem in Java

How to solve Producer Consumer Problem in Java with ExampleHere is complete Java program to solve producer consumer problem in Java programming language. In this program we have used wait and notify method from java.lang.Object class instead of using BlockingQueue for flow control.

import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Java program to solve Producer Consumer problem using wait and notify
 * method in Java. Producer Consumer is also a popular concurrency design pattern.
 *
 * @author Javin Paul
 */

public class ProducerConsumerSolution {

    public static void main(String args[]) {
        Vector sharedQueue = new Vector();
        int size = 4;
        Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");
        Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");
        prodThread.start();
        consThread.start();
    }
}

class Producer implements Runnable {

    private final Vector sharedQueue;
    private final int SIZE;

    public Producer(Vector sharedQueue, int size) {
        this.sharedQueue = sharedQueue;
        this.SIZE = size;
    }

    @Override
    public void run() {
        for (int i = 0; i < 7; i++) {
            System.out.println("Produced: " + i);
            try {
                produce(i);
            } catch (InterruptedException ex) {
                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    private void produce(int i) throws InterruptedException {

        //wait if queue is full
        while (sharedQueue.size() == SIZE) {
            synchronized (sharedQueue) {
                System.out.println("Queue is full " + Thread.currentThread().getName()
                                    + " is waiting , size: " + sharedQueue.size());

                sharedQueue.wait();
            }
        }

        //producing element and notify consumers
        synchronized (sharedQueue) {
            sharedQueue.add(i);
            sharedQueue.notifyAll();
        }
    }
}

class Consumer implements Runnable {

    private final Vector sharedQueue;
    private final int SIZE;

    public Consumer(Vector sharedQueue, int size) {
        this.sharedQueue = sharedQueue;
        this.SIZE = size;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("Consumed: " + consume());
                Thread.sleep(50);
            } catch (InterruptedException ex) {
                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    private int consume() throws InterruptedException {
        //wait if queue is empty
        while (sharedQueue.isEmpty()) {
            synchronized (sharedQueue) {
                System.out.println("Queue is empty " + Thread.currentThread().getName()
                                    + " is waiting , size: " + sharedQueue.size());

                sharedQueue.wait();
            }
        }

        //Otherwise consume element and notify waiting producer
        synchronized (sharedQueue) {
            sharedQueue.notifyAll();
            return (Integer) sharedQueue.remove(0);
        }
    }
}

Output:
Produced: 0
Queue is empty Consumer is waiting , size: 0
Produced: 1
Consumed: 0
Produced: 2
Produced: 3
Produced: 4
Produced: 5
Queue is full Producer is waiting , size: 4
Consumed: 1
Produced: 6
Queue is full Producer is waiting , size: 4
Consumed: 2
Consumed: 3
Consumed: 4
Consumed: 5
Consumed: 6
Queue is empty Consumer is waiting , size: 0

That’s all on How to solve producer consumer problem in Java using wait and notify method. I still think that using BlockingQueue to implement producer consumer design pattern is much better because of its simplicity and concise code. At the same time this problem is an excellent exercise to understand concept of wait and notify method in Java.

Other Java concurrency Interview Questions you may like

9 comments:

  1. Good one. refreshed forgotten concepts :)

    ReplyDelete
  2. There is a little bug here. In class Producer in method run should be
    "while(true){}" before for(..), because this program is ended after produce "7 producers".

    ReplyDelete
  3. There is another bug : Vector sharedQueue = new Vector();
    This initialization may not even be visible in the consumer/producer threads and may result in strange errors. This needs to initialized safely.

    ReplyDelete
  4. this is completely wrong. for example the first synchronized block in consume() checks that the vector is no longer empty, then it jumps out of the while loop. but now it's out of the sync block. so control goes to another consumer, which consumes the item. when control comes back to the original consumer, it tries to pull out of an empty vector, wrong.

    ReplyDelete
    Replies
    1. I agree, with you, synchronized block should come before while condition. I think, this is good example of How multithreading code can go wrong :)

      Delete
  5. Your code will always run into a deadlock because the producer and consumer threads will not read the true queue size. Producer thread should be modified to :
    while (true) {
    synchronized (queue) {
    while (queue.size() == size) {
    try {
    System.out
    .println("Producer thread waiting for consumer to take something from queue");
    queue.wait();
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }

    Random random = new Random();
    int i = random.nextInt();
    System.out.println("Producer putting value : " + i
    + ": in the queue");
    queue.add(i);
    queue.notifyAll();
    }

    }

    And Consumer thread to :
    while (true) {
    synchronized (queue) {
    while (queue.isEmpty()) {
    System.out
    .println("Consumer thread waiting for producer to put something in queue");
    try {
    queue.wait();
    } catch (Exception ex) {
    ex.printStackTrace();
    }

    }
    System.out.println("Consumer taking value : "
    + queue.remove(0));
    queue.notifyAll();
    }

    }

    ReplyDelete
  6. class ConsumerProducer {
    private int count;

    public synchronized void consume()
    {

    while(count == 0)
    {
    try
    {
    wait();
    }catch(InterruptedException ie)
    {
    //keep trying
    }
    count --; //consumed
    }
    }

    private synchronized void produce() {
    count++;
    notify(); // notify the consumer that count has been increm ented.
    }
    }

    ReplyDelete
  7. How about solving Producer Consumer problem using Semaphore It actually can be solved using multiple way including BlockingQueue, wait and notify as shown above, but I am really interested in using Semaphore. A good exercise to learn Semaphore in Java

    ReplyDelete
  8. Nice example. You could make it even better by using an ArrayDeque, thereby facilitating the use of addFirst() and removeLast() methods. This would simulate a Queue much better. Also I noticed that the Consumer does not need the size of the Queue. This is a dead variable. Nice work on your blog, btw. Keep it up!!!!

    ReplyDelete

Java67 Headline Animator