March 22, 2024

Producer-Consumer Problem Java Program

In this post we’ll see Java program for producer-consumer problem using threads.

Producer consumer problem

Producer consumer is a classic concurrency problem where synchronization and inter thread communication is required for proper execution.

In producer-consumer problem there are two processes Producer and Consumer sharing a common bounded buffer known as queue.

  • Producer process generates data and inserts it into the shared queue.
  • Consumer process consumes data from the shared queue.

The requirement here is that Producer should not try to add data to the shared buffer if it is already full, it should rather wait for the queue to have space for new elements. Same way, Consumer should not try to consume data from an empty buffer, it should wait for data to be inserted in the queue.

Producer-consumer Java program

Since inter-thread communication is required for the proper implementation of Producer-Consumer so this program can be written using wait-notify methods.

You can also make use of the Java concurrency package where many queue implementations are added. Using ArrayBlockingQueue you can easily implement the Producer-Consumer program in Java.

Java program for Producer-consumer using wait-notify

In the Java program a shared buffer is required that is used by both producer and consumer processes for that a LinkedList instance can be used.

There also two Runnable tasks for producer and consumer which are executed by two separate threads. Once a value is added to the queue producer should notify consumer task to wake up and should go to wait state itself.

Same way consumer task should be in wait state if queue is empty.

import java.util.LinkedList;
// Producer task
class Producer implements Runnable{
  LinkedList<Integer> list;
  Producer(LinkedList<Integer> list){
    this.list = list;
  }
  @Override
  public void run() {
    for(int i = 1; i <= 5; i++){
      synchronized(list) {
        // If there is already an element in the list wait
        while(list.size() >= 1){
          System.out.println("Waiting as queue is full..");
          try {
            list.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println("Adding to queue- " + Thread.currentThread().getName() + " " + i);
        list.add(i);
        list.notify();    
      }
    }		
  }
}
//Consumer task
class Consumer implements Runnable{
  LinkedList<Integer> list;
  Consumer(LinkedList<Integer> list){
    this.list = list;
  }
  @Override
  public void run() {
    for(int i = 1; i <= 5; i++){
      synchronized(list) {
        // if there is no element in the list wait
        while(list.size() < 1){
          System.out.println("Waiting as queue is empty..");
          try {
            list.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        // if there is element in the list then retrieve it
        System.out.println("Consuming from queue- " + Thread.currentThread().getName() + " " + list.remove());
        list.notify();  
      }
    }		
  }
}

public class ProducerConsumer {
  public static void main(String[] args) {
    // shared list
    LinkedList<Integer> list = new LinkedList<Integer>();
    Thread t1 = new Thread(new Producer(list), "Producer");
    Thread t2 = new Thread(new Consumer(list), "Consumer");
    t1.start();
    t2.start(); 
  }
}
Output
Adding to queue- Producer 1
Waiting as queue is full..
Consuming from queue- Consumer 1
Waiting as queue is empty..
Adding to queue- Producer 2
Waiting as queue is full..
Consuming from queue- Consumer 2
Waiting as queue is empty..
Adding to queue- Producer 3
Waiting as queue is full..
Consuming from queue- Consumer 3
Waiting as queue is empty..
Adding to queue- Producer 4
Waiting as queue is full..
Consuming from queue- Consumer 4
Waiting as queue is empty..
Adding to queue- Producer 5
Consuming from queue- Consumer 5

Java program for Producer-consumer using BlockingQueue

Using a BlockingQueue implementation like ArrayBlockingQueue you can easily implement the Producer-Consumer program in Java.

BlockingQueue has put() method for adding to the queue which blocks if the queue capacity is full. Same way there is a take() method for retrieving from the head of the queue which blocks if there is no element available.

In the code ArrayBlockingQueue of capacity 1 is created so queue will have only one element and the insertion will be blocked until that element is retrieved.

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// Producer task
class Producer implements Runnable{
  BlockingQueue<Integer> queue;
  Producer(BlockingQueue<Integer> queue){
    this.queue = queue;
  }
  @Override
  public void run() {
    for(int i = 1; i <= 5; i++){           
      try {
        queue.put(i);
        System.out.println("Adding to queue- " + i);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }   
    }
  }		
}
//Consumer task
class Consumer implements Runnable{
  BlockingQueue<Integer> queue;
  Consumer(BlockingQueue<Integer> queue){
    this.queue = queue;
  }
  @Override
  public void run() {
    for(int i = 1; i <= 5; i++){
      try {
        // if there is element in the list then retrieve it
        System.out.println("Consuming from queue- "  + queue.take());
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }            
  }
}

public class ProducerConsumer {
  public static void main(String[] args) {
    BlockingQueue<Integer> bQueue = new ArrayBlockingQueue<Integer>(1);
    Thread t1 = new Thread(new Producer(bQueue), "Producer");
    Thread t2 = new Thread(new Consumer(bQueue), "Consumer");
    t1.start();
    t2.start(); 
  }
}
Output
Adding to queue- 1
Consuming from queue- 1
Adding to queue- 2
Consuming from queue- 2
Adding to queue- 3
Consuming from queue- 3
Adding to queue- 4
Consuming from queue- 4
Adding to queue- 5
Consuming from queue- 5

As you can see using ArrayBlockingQueue you don’t need to write logic for synchronizing threads and call wait and notify explicitly making it very simple to write producer-consumer Java program. It can be made more compact using Lambda expression.

public class ArrayBQ {
  public static void main(String[] args) {
    // BlockingQueue of capacity 1
    BlockingQueue<Integer> bQueue = new ArrayBlockingQueue<Integer>(1);
    // Producer 
    new Thread(()->{
      for(int i = 0; i < 5; i++){
        try {
          bQueue.put(i);
          System.out.println("Added to queue-" + i);  
          
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    }).start();
        
    // Consumer
    new Thread(()->{
      for(int i = 0; i < 5; i++){
        try {
          System.out.println("Consumer retrieved- " + bQueue.take());
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    }).start();
  }
}

That's all for the topic Producer-Consumer Problem Java Program. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment