May 25, 2022

Blog @ Munaf Sheikh

Latest news from tech-feeds around the world.

Producer Consumer Problem with Wait and Notify – Thread Example Tutorial


The Producer-Consumer Problem is a classical concurrency problem and in fact, it is one of the most powerful concurrency design patterns which is used in most multithreaded Java applications. In the last article, I have shown you how to solve the Producer-Consumer problem in Java using blocking Queue but one of my readers emailed me and requested a code example and explanation of solving the Producer-Consumer problem in Java with the wait and notify method as well Since it’s often asked as one of the top coding questions in Java. In this Java tutorial, I have put the code example of the wait notify version of the earlier producer-consumer concurrency design pattern. 

You can see this is a much longer code with explicit handling blocking conditions like when the shared queue is full and when the queue is empty which can be tricky for anyone. Since we have replaced BlockingQueue
with Vector we need to implement blocking using wait
and notify
and that’s why we have introduced the 
produce(int i) and consume() method.

If
you see I have kept the consumer thread a little slow by allowing it to sleep for 50 Milliseconds to give an opportunity to the producer to fill the queue, which helps
to understand that the Producer thread is also waiting when Queue is full.


By the way, if you are new to multithreading in Java then I also suggest you join a course like Multithreading and Parallel Computing in Java from Udemy. It’s a great course to learn the multithreading basics and become a better Java developer.  



Java program to solve Producer-Consumer Problem in Java

Here is a complete Java program to solve the classic producer-consumer problem in the Java
programming language. In this program, we have used the wait and notify method from

java.lang.Object
class instead of using BlockingQueue for flow control which makes implementing producer-consumer pattern really easy.

Before solving the problem, let’s revisit what is the producer-consumer problem first?

This is a classical synchronization problem that involves a fixed size buffer or queue which can have items or task added to it or removed from it by different producer and consumer threads. The key is to solve the problem in such a way that the producer should wait if the queue is full and the consumer should wait if the queue is empty which involves inter-thread communication. 

This problem is also known by different names in the technical world like a consumer-producer problem, bounded buffer problem, or a blocking queue problem. I highly recommend you master this problem to improve your concurrency skills.

If you need more such problems I suggest solving the multithreading problems given in Java Multithreading for Senior Engineering Interviews on Educative. It’s an interactive course that teaches you how to solve classical concurrency problems like producer-consumer, dining philosophers, Barber shop problems, and Uber Ride problems. 

Now, let’s see the code and try to understand how does it work:

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 the queue is
full

        while (sharedQueue.size() == SIZE) {
            synchronized
(sharedQueue)
{
                System.out.println(“The 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 the queue is
empty

        while (sharedQueue.isEmpty()) {
            synchronized
(sharedQueue)
{
                System.out.println(“The queue is empty
+ Thread.currentThread().getName()

                                    + ” is waiting ,
size: “
+ sharedQueue.size());

                sharedQueue.wait();
            }
        }

        //Otherwise consume
element and notify the waiting producer

        synchronized
(sharedQueue)
{
            sharedQueue.notifyAll();
            return
(Integer)
sharedQueue.remove(0);
        }
    }
}

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

If you look at the output there are a couple of points which is worth noting:

1. Both Producer and Consumer thread can run in any order, even though you have started it doesn’t mean that the producer will produce first and then the consumer will consume. Whenever the producer gets a chance it keeps producing until the queue is full, the same goes with the consumer. 

2. You can see that the Consumer is waiting when the queue is empty and the Producer is waiting when the queue is full which is the expected behavior. 

3. Any change to the shared object, here sharedQueue which is a vector is happening inside the synchronized block so that changes made by one thread are visible to the other. Remember whenever a thread exit or enter a synchronized block, the memory barrier is refreshed. 

This is the standard behavior of the Java memory model and I highly recommend you to read Java Concurrency in Practice – The Book at least once to learn more about memory barriers, Java Memory Model, and happens-before in Java. 
Producer consumer pattern in Java using wait and notify

How to solve Producer Consumer Problem in Java with ExampleThat’s all on How to solve the producer-consumer problem in Java using the wait and notify method. I still think that using BlockingQueue to
implement producer-consumer design patterns is much better because of its
simplicity and concise code. At the same time, this problem is an excellent
exercise to understand the concept of the wait and notify method in Java.
 Other Java Multithreading and Concurrency Articles you may like

  • How to do inter-thread communication in Java using wait-notify? (answer)
  • How to pause a Thread in Java? (solution)
  • 5 Courses to Learn Java Multithreading in-depth (courses)
  • Difference between volatile, synchronized, and atomic variable in Java (answer)
  • 10 Java Multithreading and Concurrency Best Practices (article)
  • Top 5 Books to Master Concurrency in Java (books)
  • Top 50 Multithreading and Concurrency Questions in Java (questions)
  • How to avoid deadlock in Java? (answer)
  • Difference between CyclicBarrier and CountDownLatch in Java? (answer)
  • Difference between ForkJoinPool and Executor Framework in Java(answer)
  • 5 Essential Skills to Crack Java Interviews (skills)
  • Understanding the flow of data and code in Java program (answer)
  • How to join two threads in Java? (answer)
  • Difference between Executor and ExecutorService in Java? (answer)
  • How to stop a Thread in Java? (answer)
  • What is Happens Before in Java Concurrency? (answer)


Thanks a lot for reading this article so far. If you like this example of solving the producer-consumer problem using wait and notify in Java then please share it with your friends and colleagues. If you have any questions or feedback then please drop a note.

P. S. – If you are new but what to learn Java Multithreading and Concurrency and looking for a free course to start with then I also, suggest you check out this awesome free Java Multithreading course on Udemy. 



Source link