Producer–Consumer Problem in Java Using BlockingQueue

The Producer–Consumer problem is one of the most important multi-threading patterns in Java. Traditionally, developers used wait and notify to coordinate threads, but this approach is complex and error-prone. Modern Java provides a cleaner solution: BlockingQueue.

1. What is the Producer–Consumer Problem?

Two types of threads share a common buffer:

  • Producer → Generates data and puts it into the buffer
  • Consumer → Retrieves data from the buffer and processes it

The challenge: prevent producers from writing when the buffer is full and consumers from reading when the buffer is empty.

2. Problems with wait()/notify()

The older approach required:

  • Manual locking
  • Complex condition handling
  • Risk of deadlocks
  • Hard to debug

BlockingQueue solves these issues with built-in thread management.

3. Why BlockingQueue?

BlockingQueue handles synchronization internally. Its key methods:

  • put() – waits if the queue is full
  • take() – waits if the queue is empty

No need for wait(), notify(), or synchronized blocks.

4. Example Implementation (Producer + Consumer)

import java.util.concurrent.*;

public class ProducerConsumerDemo {

    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue<>(5);

        Runnable producer = () -> {
            int value = 1;
            try {
                while (true) {
                    queue.put(value);
                    System.out.println("Produced: " + value);
                    value++;
                    Thread.sleep(500);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        Runnable consumer = () -> {
            try {
                while (true) {
                    int val = queue.take();
                    System.out.println("Consumed: " + val);
                    Thread.sleep(800);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

5. Multiple Producers & Consumers

ExecutorService service = Executors.newFixedThreadPool(4);

service.submit(producer);
service.submit(producer);
service.submit(consumer);
service.submit(consumer);

6. Graceful Shutdown Strategy

Use a poison pill:

queue.put(-1); // special value indicating stop

Consumers stop when they read -1.

7. Real-World Use Cases

  • Logging frameworks
  • Thread pool work queues
  • Messaging systems
  • Data streaming pipelines
  • Producer–Consumer microservices

Conclusion

BlockingQueue simplifies the producer–consumer problem by handling all synchronization internally. It is modern, cleaner, thread-safe, and widely used in enterprise applications.

Comments

Popular posts from this blog

Spring Boot on AWS EC2: Upload to S3 Securely Using IAM Role

Java Streams Intermediate Operations Explained with Examples

ConcurrentHashMap vs Synchronized HashMap in Java