Java Programs›Multithreading›Producer-Consumer Problem

Producer-Consumer Problem in Java

advanced·  Multithreading  ·  Concurrency

Problem

In the producer-consumer problem one thread creates items and another processes them through a shared buffer of limited size; the producer must wait when it's full and the consumer when it's empty.

Have a producer thread create the items 1 to 5 and a consumer thread process them, using a buffer that holds at most two items.

Input
items 1 to 5, buffer size 2
Output
Consumed 1 ... Consumed 5

Java Program

Java
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class ProducerConsumerQueue { private static final int DONE = -1; // a "poison pill" telling the consumer to stop public static void main(String[] args) throws InterruptedException { BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2); // holds at most 2 items Thread producer = new Thread(() -> { try { for (int item = 1; item <= 5; item++) { queue.put(item); // waits if the queue is full } queue.put(DONE); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); Thread consumer = new Thread(() -> { try { while (true) { int item = queue.take(); // waits if the queue is empty if (item == DONE) break; System.out.println("Consumed " + item); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); producer.start(); consumer.start(); producer.join(); consumer.join(); System.out.println("All items processed"); } }

Output

Consumed 1 Consumed 2 Consumed 3 Consumed 4 Consumed 5 All items processed

Core Logic

A BlockingQueue handles all the waiting: put pauses the producer when the queue is full, and take pauses the consumer when it's empty.

How It Works
  1. 1new ArrayBlockingQueue&lt;&gt;(2) is the shared, bounded buffer.
  2. 2The producer calls queue.put(item) for 1 to 5, then puts the special value DONE.
  3. 3The consumer loops on queue.take(), printing each item until it receives DONE.
  4. 4Main joins both threads before printing the final message.
With room for only two items, the producer regularly blocks on put until the consumer frees a slot — yet items always come out in order 1 to 5.
💡

Key Point: The "poison pill" (DONE = -1) is a clean way to tell a consumer that no more work is coming, without interrupting it.

Key Concepts

BlockingQueueput()take()poison pill

Approach 2: Using wait() and notifyAll()

Java
import java.util.LinkedList; import java.util.Queue; public class ProducerConsumerWaitNotify { private final Queue<Integer> buffer = new LinkedList<>(); private final int capacity = 2; synchronized void put(int item) throws InterruptedException { while (buffer.size() == capacity) { wait(); // releases the lock and sleeps until notified } buffer.add(item); notifyAll(); // wake any waiting consumer } synchronized int take() throws InterruptedException { while (buffer.isEmpty()) { wait(); } int item = buffer.remove(); notifyAll(); // wake any waiting producer return item; } public static void main(String[] args) throws InterruptedException { ProducerConsumerWaitNotify shared = new ProducerConsumerWaitNotify(); Thread producer = new Thread(() -> { try { for (int item = 1; item <= 5; item++) shared.put(item); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); Thread consumer = new Thread(() -> { try { for (int i = 0; i < 5; i++) System.out.println("Consumed " + shared.take()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); producer.start(); consumer.start(); producer.join(); consumer.join(); System.out.println("All items processed"); } }

Output

Consumed 1 Consumed 2 Consumed 3 Consumed 4 Consumed 5 All items processed

Core Logic

The classic low-level version builds the bounded buffer by hand, using an object's monitor to make threads wait and wake each other.

How It Works
  1. 1put and take are synchronized on the shared object.
  2. 2put calls wait() while the buffer is full; take calls wait() while it's empty.
  3. 3After changing the buffer, each method calls notifyAll() so a waiting thread can re-check its condition.
  4. 4The consumer takes exactly five items, so no poison pill is needed.
If the consumer runs first, it finds the buffer empty and sleeps inside wait() until the producer's notifyAll() wakes it.
💡

Key Point: Always call wait() inside a while loop, not an if — a woken thread must re-check the condition, because another thread may have changed it first.

Key Concepts

wait()notifyAll()synchronizedguarded while loop

Interview Questions

What happens if the consumer is slower than the producer?

The bounded queue fills up and put blocks the producer until there's space, which naturally slows production to match.

How would you support several consumers?

Start more consumer threads on the same queue and send one poison pill per consumer, so each one stops.

Learn the Concept

Related Programs