Producer-Consumer Pattern Using Wait-Notify Mechanism in Java

  1. Conceptual Analysis

1.1 Introduction

The producer-consumer pattern is also known as the wait-notify mechanism. This is a classic multi-threading cooperation pattern in Java. Understanding this mechanism will provide deeper insight into multi-threaded execution behavior.

As previously learned, thread execution is inherently random. With two threads running, the output could be completely unpredictable. However, the wait-notify mechanism changes this behavior by enforcing alternating execution between threads. This ensures a controlled execution pattern where threads take turns: producer executes once, then consumer executes once, and so on.

One thread serves as the producer responsible for generating data, while the other thread acts as the consumer responsible for consuming that data.

To understand the complex logic, let's use a real-world analogy. Consider two people: a diner and a chef.

The diner is the consumer, and the chef is the producer. However, these two alone are insufficient. We need a third entity - a table - to control thread execution, since by default thread execution is random.

Assume there's a bowl of noodles on the table. If noodles exist, the diner executes to consume them. If no noodles exist, the chef executes to prepare them.

1.2 Scenario One: Consumer Waiting

In the ideal scenario, the chef acquires CPU execution first. Since the table is empty, the chef prepares noodles and places them on the table, then the diner consumes them. This creates a pattern where the chef produces one item and the diner consumes one item.

However, programs don't always behave as expected. Thread execution is random, so we must consider all possible scenarios. Fortunately, there are only two main scenarios to handle.

Let's analyze the first scenario: consumer waiting.

Assume the diner acquires CPU execution first. Since the table is empty, the diner cannot proceed - it must wait. In code, this is called wait(). Once the diner waits, the chef will inevitably acquire CPU execution. The chef checks the table, finds no noodles, prepares a bowl, and places it on the table.

After preparation, the chef must wake the waiting diner to eat. This action is called notify. Once awakened, the diner begins consuming.

The core logic is: check the table - if no noodles exist, the consumer waits.

1.3 Scenario Two: Producer Waiting

Now consider the second scenario: producer waiting.

If the chef acquires CPU execution first when the table is empty, the chef performs the three steps: prepare food, place on tible, and notify the waiting consumer.

However, if noone is waiting (which is fine - the notification simply has no effect), and the chef continues to acquire CPU execution again, the chef cannot prepare more food since the table already has noodles. The chef must wait.

This requires adding a check in the producer logic. When the chef waits, the diner will acquire CPU execution. The diner checks if food exists - if not, it waits; if yes, it consumes and then notifies the chef to continue producing.

1.4 Important Methods

Three key methods are involved in this mechanism:

Method Description
void wait() Causes current thread to wait until another thread invokes notify() or notifyAll() on this object
void notify() Wakes up a single random thread waiting on this object's monitor
void notifyAll() Wakes up all threads waiting on this object's monitor
  1. SharedResource Class Implementation

Based on the analysis above, we need at least three components: producer, consumer, and a shared resource that controls both.

public class SharedResource {

    /*
     * Purpose: Controls producer and consumer execution
     */

    // How to control? Use a status flag: 0 means no food, 1 means food present
    // Why use int instead of boolean? Boolean has only two values and can only control two threads.
    // For future scalability (controlling 3+ threads), int provides more flexibility
    public static int foodStatus = 0;

    // Maximum number of items to produce/consume
    public static int totalCount = 10;

    // Lock object for synchronization
    public static Object lock = new Object();
}

  1. Consumer Implementation

When writing multi-threaded code, follow these four steps:

1. Loop
2. Synchronized block (can later be refactored to synchronized method or Lock)
3. Check if shared data has reached the end (handle end condition first - simpler)
4. If not at end, execute core logic

Here's the consumer implementation:

public class Consumer extends Thread {

    @Override
    public void run() {
        // 1. Loop
        while (true) {
            // 2. Synchronized block
            synchronized (SharedResource.lock) {
                // 3. Check if all items have been consumed
                if (SharedResource.totalCount == 0) {
                    break;
                } else {
                    // 4. Not finished - execute core logic
                    if (SharedResource.foodStatus == 0) {
                        // No food available - wait
                        try {
                            // Must call wait() on the lock object to associate thread with the lock
                            // This allows notifyAll() to properly wake threads bound to this lock
                            SharedResource.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // Food is available - consume it
                        SharedResource.totalCount--;
                        System.out.println("Consumer ate noodles. Remaining: " + SharedResource.totalCount);
                        
                        // Notify producer to continue
                        SharedResource.lock.notifyAll();
                        
                        // Update table status
                        SharedResource.foodStatus = 0;
                    }
                }
            }
        }
    }
}

  1. Producer Implementation

The producer also follows the same four-step pattern:

1. Loop
2. Synchronized block
3. Check if production is complete
4. If not complete, execute core logic

Here's the producer implementation:

public class Producer extends Thread {
    @Override
    public void run() {
        // 1. Loop
        while (true) {
            // 2. Synchronized block
            synchronized (SharedResource.lock) {
                // 3. Check if production is complete
                if (SharedResource.totalCount == 0) {
                    break;
                } else {
                    // Not complete
                    if (SharedResource.foodStatus == 1) {
                        // Food already exists - wait for consumer to finish
                        try {
                            SharedResource.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // No food - produce some
                        System.out.println("Producer made noodles");
                        
                        // Update food status
                        SharedResource.foodStatus = 1;
                        
                        // Wake up waiting consumer
                        SharedResource.lock.notifyAll();
                    }
                }
            }
        }
    }
}

  1. Testing

public static void main(String[] args) {
    // Create thread objects
    Producer p = new Producer();
    Consumer c = new Consumer();

    // Set thread names
    p.setName("Producer");
    c.setName("Consumer");

    // Start threads
    p.start();
    c.start();
}

Upon execution, the output demonstrates the expected alternating pattern: producer makes noodles, consumer eats noodles, repeatedly until all items are consumed. The program terminates gracefully when the count reaches zero.

Tags: java multithreading Concurrency producer-consumer wait-notify

Posted on Tue, 22 Sep 2026 16:10:45 +0000 by pacognovellino