The Producer - Consumer pattern is based on the idea of separating the production and consumption of data. Producers generate data and put it into a shared buffer, and consumers take data from the buffer for processing. The shared buffer acts as a communication channel between producers and consumers.
There are two main challenges in implementing this pattern:
wait()
and notify()
The wait()
and notify()
methods are part of the Java Object
class. wait()
is used to make a thread pause and release the lock, and notify()
is used to wake up a waiting thread.
import java.util.LinkedList;
import java.util.Queue;
class ProducerConsumerUsingWaitNotify {
private static final int MAX_SIZE = 5;
private Queue<Integer> buffer = new LinkedList<>();
class Producer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (buffer) {
while (buffer.size() == MAX_SIZE) {
try {
buffer.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
int item = (int) (Math.random() * 100);
buffer.add(item);
System.out.println("Produced: " + item);
buffer.notify();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (buffer) {
while (buffer.isEmpty()) {
try {
buffer.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
int item = buffer.poll();
System.out.println("Consumed: " + item);
buffer.notify();
}
}
}
}
public static void main(String[] args) {
ProducerConsumerUsingWaitNotify pc = new ProducerConsumerUsingWaitNotify();
Thread producerThread = new Thread(pc.new Producer());
Thread consumerThread = new Thread(pc.new Consumer());
producerThread.start();
consumerThread.start();
}
}
BlockingQueue
BlockingQueue
is a thread - safe queue that provides blocking operations. It simplifies the implementation of the Producer - Consumer pattern by handling synchronization and coordination automatically.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class ProducerConsumerUsingBlockingQueue {
private static final int MAX_SIZE = 5;
private BlockingQueue<Integer> buffer = new LinkedBlockingQueue<>(MAX_SIZE);
class Producer implements Runnable {
@Override
public void run() {
while (true) {
try {
int item = (int) (Math.random() * 100);
buffer.put(item);
System.out.println("Produced: " + item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
try {
int item = buffer.take();
System.out.println("Consumed: " + item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public static void main(String[] args) {
ProducerConsumerUsingBlockingQueue pc = new ProducerConsumerUsingBlockingQueue();
Thread producerThread = new Thread(pc.new Producer());
Thread consumerThread = new Thread(pc.new Consumer());
producerThread.start();
consumerThread.start();
}
}
InterruptedException
properly. When a thread is interrupted, it should clean up any resources and exit gracefully.BlockingQueue
: Using BlockingQueue
is generally preferred over wait()
and notify()
because it simplifies the code and reduces the chance of programming errors.The Producer - Consumer pattern is a powerful concurrent design pattern that can improve the performance and resource utilization of Java applications. In Java, we can implement this pattern using wait()
and notify()
or BlockingQueue
. While wait()
and notify()
provide a low - level way to implement synchronization and coordination, BlockingQueue
simplifies the implementation and is more error - prone. By following common and best practices, we can ensure that our Producer - Consumer implementation is robust and efficient.