Producer Consumer Problem in Java Using BlockingQueue

Producer Consumer design pattern is an example of concurrency pattern in Java. The solution of the producer consumer problem can be classic wait and notify thread operations that are commonly used in multi-threading but in this article, we will deal with this by using the BlockingQueue implementation which was introduced in Java 5.

The BlockingQueue interface can be found inside the java.util.concurrent package. There are many BlockingQueue implementations provided by Java like LinkedBlockingQueue, PriorityBlockingQueue, ArrayBlockingQueue and SynchronousQueue which are all thread safe.

Read Also :  What is Thread Synchronization in Java

Introduction

Producer Consumer design pattern has two threads namely: the Producer and the Consumer. These threads are linked by a common buffer which is basically a queue.
Both the threads are independent in their nature of work. The producer will generate the data, put it in the queue and keep on repeating the same process. The consumer will keep on consuming or using the data by removing it from the queue.

Producer Consumer Problem:

The main problem to understand here is if the queue or buffer is full, the producer should not try to put any more data on it. Similarly, if the queue is empty then the consumer should not try to extract or remove data from the empty queue.

Solution:

The BlockingQueue interface provides two methods put() and take() which are used implicitly in blocking the Producer and the Consumer thread respectively. The thread (Consumer) trying to remove item from an empty queue waits or is blocked until the Producer thread adds an item to the queue. Similarly, the thread (Producer) trying to add an item to a full queue is blocked until any Consumer thread makes some space in the queue by removing the item(s) from the queue.

Producer Consumer Solution using BlockingQueue Example:

Producer Class

The code snippet given below represents a typical Producer class generating some data and putting it in the BlockingQueue. If the buffer is full, it will wait for the Consumer thread to remove some data from the queue before resuming its operation again. We are trying to generate some Integer value as the data here in this example.

package com.javahungry; 
import java.util.concurrent.BlockingQueue;

class Producer implements Runnable {

 private final BlockingQueue<Integer> blockingQueue;

 public Producer(BlockingQueue<Integer> blockingQueue) {
  this.blockingQueue = blockingQueue;
 }

 public void run() {
  for (int i = 10; i <= 100; i += 10) {
   try {
      System.out.println("Data produced : " + i);
      blockingQueue.put(i);
    } catch (InterruptedException ex) {
      System.out.println("Producer thread interrupted.");
    }
   }
  }
}


Consumer Class

The code snippet given below represents a Consumer class that removes the data from queue and does some processing on it. If the buffer is empty, it will wait until the Producer thread generates and puts some data into the queue.


package com.javahungry; 
import java.util.concurrent.BlockingQueue;

class Consumer implements Runnable {

 private final BlockingQueue<Integer> blockingQueue;

 public Consumer(BlockingQueue<Integer> blockingQueue) {
  this.blockingQueue = blockingQueue;
 }

 public void run() {
  while (true) {
   try {
    System.out.println("Data consumed : " + blockingQueue.take());
   } catch (InterruptedException ex) {
    System.out.println("Consumer thread interrupted.");
   }
  }
 }
}


Testing the Solution using main class:

We will now test the solution that we have created using BlockingQueue. As you can see below, we have created a shared BlockingQueue in which the Producer thread will put the data and the same data will be consumed by the Consumer thread. Here, we have used LinkedBlockingQueue class to implement the BlockingQueue interface. Further, we create the Producer and the Consumer thread, start them and observe the result.


package com.javahungry;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ProducerConsumerExample {

 public static void main(String[] args) {

  // Common buffer created using LinkedBlockingQueue
  BlockingQueue<Integer> blockingQueue =  
                                  new LinkedBlockingQueue<Integer>();

  // Producer thread creation
  Thread producer = new Thread(new Producer(blockingQueue));

  // Consumer thread creation
  Thread consumer = new Thread(new Consumer(blockingQueue));

  // Start Producer and Consumer thread
  producer.start();
  consumer.start();

 }

}


Output:
Data produced : 10
Data produced : 20
Data consumed : 10
Data produced : 30
Data consumed : 20
Data produced : 40
Data consumed : 30
Data produced : 50
Data consumed : 40
Data consumed : 50
Data produced : 60
Data produced : 70
Data consumed : 60
Data produced : 80
Data consumed : 70
Data produced : 90
Data produced : 100
Data consumed : 80
Data consumed : 90
Data consumed : 100

It is evident from the above output that the data is getting generated and consumed smoothly in a First-In, First-Out order as it is the feature of LinkedBlockingQueue. The Producer thread is creating the data, putting it in the queue and Consumer thread is removing it from the data implicitly without any need to write and notify statements. This wraps up this article on Producer Consumer Problem in Java using multithreading (BlockingQueue).

References:
Java doc

About The Author

Subham Mittal has worked in Oracle for 3 years.
Enjoyed this post? Never miss out on future posts by subscribing JavaHungry