Java - Producer Consumer Synchronization Problem

Introduction

The producer/consumer is a typical thread synchronization problem that uses the wait() and notify() methods.

There are four classes: Buffer, Producer, Consumer, and Main.

Buffer class has an integer data element that will be produced by the producer and consumed by the consumer.

The code would synchronize the access to the buffer, so that:

  • Producer produces a new data element only when the Buffer is empty
  • Consumer consumes the buffer's data only when it is available.

Buffer class has two instance variables:

private int data
private boolean empty

producer uses the data instance variable to store the new data.

consumer uses it to read the data.

The empty instance variable is used as an indicator whether the buffer is empty or not.

import java.util.Random;

class Buffer {
  private int data;
  private boolean empty;

  public Buffer() {
    this.empty = true;
  }

  public synchronized void produce(int newData) {
    // Wait until the buffer is empty
    while (!this.empty) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    // Store the new data produced by the producer
    this.data = newData;

    // Set the empty flag to false, so the consumer may consume the data
    this.empty = false;

    // Notify the waiting consumer in the wait set
    this.notify();

    System.out.println("Produced:" + newData);
  }

  public synchronized int consume() {
    // Wait until the buffer gets some data
    while (this.empty) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    // Set the empty flag to true, so that the producer can store new data
    this.empty = true;

    // Notify the waiting producer in the wait set
    this.notify();

    System.out.println("Consumed:" + data);

    return data;
  }
}

class Producer extends Thread {
  private Buffer buffer;

  public Producer(Buffer buffer) {
    this.buffer = buffer;
  }

  public void run() {
    Random rand = new Random();
    while (true) {
      // Generate a random integer and store it in the buffer
      int n = rand.nextInt();
      buffer.produce(n);
    }
  }
}

class Consumer extends Thread {
  private Buffer buffer;

  public Consumer(Buffer buffer) {
    this.buffer = buffer;
  }

  public void run() {
    int data;
    while (true) {
      data = buffer.consume();
    }
  }
}

public class Main {
  public static void main(String[] args) {
    Buffer buffer = new Buffer();
    Producer p = new Producer(buffer);
    Consumer c = new Consumer(buffer);
    p.start();
    c.start();
    
  }
}