Overview
The Producer-Consumer Design Pattern is a concurrency pattern where producers generate data and place it into a shared resource (e.g., a queue), while consumers retrieve and process the data. The pattern ensures synchronization and prevents race conditions between producers and consumers.
Key Characteristics
- Decouples the producers and consumers, allowing them to operate independently.
- Uses a shared resource (e.g., a queue) to store data between producers and consumers.
- Ensures thread safety and synchronization to avoid race conditions.
Implementation
The following is an example of a Producer-Consumer implementation in Java:
import java.util.LinkedList;
import java.util.Queue;
class Producer implements Runnable {
private final Queue queue;
private final int MAX_CAPACITY;
public Producer(Queue queue, int maxCapacity) {
this.queue = queue;
this.MAX_CAPACITY = maxCapacity;
}
@Override
public void run() {
int value = 0;
while (true) {
synchronized (queue) {
while (queue.size() == MAX_CAPACITY) {
try {
queue.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.println("Produced: " + value);
queue.add(value++);
queue.notifyAll();
}
}
}
}
class Consumer implements Runnable {
private final Queue queue;
public Consumer(Queue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
int value = queue.poll();
System.out.println("Consumed: " + value);
queue.notifyAll();
}
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
Queue queue = new LinkedList<>();
int maxCapacity = 5;
Thread producerThread = new Thread(new Producer(queue, maxCapacity));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
When to Use
- When you need to balance workloads between multiple producers and consumers.
- When you want to decouple data production and consumption processes.
Advantages
- Improves application scalability and responsiveness by decoupling producers and consumers.
- Prevents race conditions with proper synchronization.
- Ensures efficient utilization of resources.
Disadvantages
- May require careful tuning of the buffer size and thread count to balance workloads.
- Can introduce latency if producers or consumers become bottlenecks.