Producer-Consumer problem in Java

Published on August 7, 2024

Example Producer-Consumer problem in Java using a shared buffer and basic synchronization:

The Producer-Consumer problem involves multiple threads: producers adding data items to a shared buffer and consumers removing them. The challenge is to synchronize their access to the buffer, ensuring producers wait when it's full and consumers wait when it's empty, while preventing race conditions and ensuring efficient thread communication

import java.util.LinkedList;
import java.util.Queue;
 
class SharedBuffer {
    private Queue<Integer> buffer = new LinkedList<>();
    private int capacity;
 
    public SharedBuffer(int capacity) {
        this.capacity = capacity;
    }
 
    public synchronized void produce(int item) throws InterruptedException {
        while (buffer.size() == capacity) {
            wait(); // Wait if buffer is full
        }
        buffer.offer(item);
        System.out.println("Produced: " + item);
        notify(); // Notify consumer that new item is produced
    }
 
    public synchronized int consume() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait(); // Wait if buffer is empty
        }
        int item = buffer.poll();
        System.out.println("Consumed: " + item);
        notify(); // Notify producer that an item is consumed
        return item;
    }
}
 
class Producer extends Thread {
    private SharedBuffer buffer;
 
    public Producer(SharedBuffer buffer) {
        this.buffer = buffer;
    }
 
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            try {
                buffer.produce(i);
                Thread.sleep((int) (Math.random() * 100)); // Simulate some work
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
 
class Consumer extends Thread {
    private SharedBuffer buffer;
 
    public Consumer(SharedBuffer buffer) {
        this.buffer = buffer;
    }
 
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            try {
                int item = buffer.consume();
                Thread.sleep((int) (Math.random() * 100)); // Simulate some work
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
 
public class Main {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer(3); // Buffer capacity of 3
        Producer producer = new Producer(buffer);
        Consumer consumer = new Consumer(buffer);
 
        producer.start();
        consumer.start();
    }
}
Explanation:
  • SharedBuffer: Manages a shared buffer (Queue<Integer> buffer) with a specified capacity. It provides synchronized methods produce(int item) and consume() to add items to and remove items from the buffer respectively. It uses wait() and notify() for synchronization.

  • Producer: Extends Thread and produces (adds) items to the shared buffer using produce(int item) method of SharedBuffer.

  • Consumer: Also extends Thread and consumes (removes) items from the shared buffer using consume() method of SharedBuffer.

  • Main: Creates an instance of SharedBuffer with capacity 3, and instances of Producer and Consumer. Starts their threads to run concurrently.

Key Points:
  • The produce() method checks if the buffer is full (buffer.size() == capacity) and waits (wait()) if true. Once an item is produced, it adds (offer()) it to the buffer and notifies (notify()) the consumer.

  • The consume() method checks if the buffer is empty (buffer.isEmpty()) and waits (wait()) if true. Once an item is consumed, it removes (poll()) it from the buffer and notifies (notify()) the producer.

  • wait() and notify() methods are used for synchronization to ensure that producers and consumers wait appropriately when the buffer is full or empty, and to avoid race conditions.