Java案例如何实现生产者消费者?从原理到实战,一文精通并发编程核心模式
📖 目录导读
- 什么是生产者消费者模式?——核心概念与业务价值
- 为什么Java中实现该模式要特别小心?——线程安全与同步机制
- 经典实现方式一:使用
synchronized+wait/notify(最传统方案) - 经典实现方式二:使用
BlockingQueue(最推荐的生产级方案) - 进阶实现方式三:使用
Lock+Condition(更灵活的并发控制) - 实战案例:模拟订单处理系统(完整可运行代码)
- 常见问题FAQ:死锁、数据丢失、性能瓶颈如何解决?
- 总结与最佳实践建议
1️⃣ 什么是生产者消费者模式?
生产者消费者模式是Java并发编程中最经典的协作模式之一,它解决的是“两个或多个线程如何安全地共享一个数据缓冲区”的问题。

- 生产者:负责生成数据,并将其放入共享缓冲区(如队列)
- 消费者:负责从缓冲区取出数据并进行处理
- 缓冲区:作为中间桥梁,解耦生产与消费的速度差异
典型业务场景:
- 日志收集系统(生产者写入日志,消费者异步写入文件)
- 订单处理系统(用户下单是生产者,后台处理是消费者)
- 爬虫任务队列(URL爬取与解析分离)
2️⃣ 为什么Java中实现该模式要特别小心?
问题:如果不加控制,多个线程同时操作共享资源会导致什么后果?
核心风险:
- 数据竞争:多个线程同时修改队列,导致数据错乱
- 活锁/死锁:生产者等待消费者消费,消费者等待生产者生产
- 性能瓶颈:频繁的线程上下文切换或锁竞争
Java并发编程中,实现生产者消费者模式需要解决三个关键问题:
- 互斥访问:同一时刻只有一个线程操作缓冲区
- 条件等待:缓冲区满时,生产者必须等待;缓冲区空时,消费者必须等待
- 通知机制:状态变化后,及时唤醒等待的线程
3️⃣ 经典实现方式一:synchronized + wait/notify
这是最基础的实现方式,适合理解核心原理。
public class TraditionalProducerConsumer {
private static final int MAX_CAPACITY = 10;
private final LinkedList<Integer> queue = new LinkedList<>();
public synchronized void produce(int item) throws InterruptedException {
while (queue.size() == MAX_CAPACITY) {
wait(); // 缓冲区满,生产者等待
}
queue.add(item);
notifyAll(); // 唤醒可能等待的消费者
}
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 缓冲区空,消费者等待
}
int item = queue.removeFirst();
notifyAll(); // 唤醒可能等待的生产者
return item;
}
}
注意事项:
- 必须使用
while循环检查条件,而不是if(防止虚假唤醒) notifyAll()比notify()更安全(避免信号丢失)- 锁对象必须是同一个(这里是
this)
4️⃣ 经典实现方式二:BlockingQueue(生产级首选)
问题:手写
wait/notify容易出错,有没有更简单的方案?
答案:使用java.util.concurrent.BlockingQueue接口的实现类,如ArrayBlockingQueue、LinkedBlockingQueue。
public class BlockingQueueDemo {
private static final int QUEUE_SIZE = 100;
private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(QUEUE_SIZE);
public void produce(int item) throws InterruptedException {
queue.put(item); // 如果队列满,自动阻塞等待
}
public int consume() throws InterruptedException {
return queue.take(); // 如果队列空,自动阻塞等待
}
}
优势:
- 线程安全,无需手动管理锁和条件
- 支持阻塞与非阻塞方法(
offer/poll) - 性能优化(如
LinkedBlockingQueue使用分离锁)
5️⃣ 进阶实现方式三:Lock + Condition
当需要更精细的并发控制时(如限制生产速率、优先消费),可以使用ReentrantLock + Condition。
public class LockBasedProducerConsumer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final LinkedList<Integer> queue = new LinkedList<>();
private final int capacity;
public void produce(int item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 生产者等待“不满”条件
}
queue.add(item);
notEmpty.signal(); // 唤醒一个等待的消费者
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 消费者等待“不空”条件
}
int item = queue.removeFirst();
notFull.signal(); // 唤醒一个等待的生产者
return item;
} finally {
lock.unlock();
}
}
}
与synchronized的区别:
Condition支持多个条件变量,避免notifyAll的无效唤醒- 更灵活的唤醒策略(如
signalvssignalAll)
6️⃣ 实战案例:模拟订单处理系统
下面是一个完整的可运行案例,模拟电商订单处理:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class OrderProcessingSystem {
private static final int QUEUE_CAPACITY = 50;
private final BlockingQueue<Order> orderQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
private final AtomicInteger orderIdGenerator = new AtomicInteger(0);
// 订单类
static class Order {
private final int id;
private final String productName;
public Order(int id, String productName) {
this.id = id;
this.productName = productName;
}
@Override
public String toString() {
return "Order{id=" + id + ", product='" + productName + "'}";
}
}
// 生产者线程
class Producer implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
int orderId = orderIdGenerator.incrementAndGet();
Order order = new Order(orderId, "Product-" + orderId);
orderQueue.put(order); // 阻塞放入
System.out.println("生产: " + order + " (队列剩余: " + orderQueue.remainingCapacity() + ")");
Thread.sleep((long)(Math.random() * 500)); // 模拟生产延迟
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者线程
class Consumer implements Runnable {
private final String consumerName;
public Consumer(String name) { this.consumerName = name; }
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
Order order = orderQueue.take(); // 阻塞获取
System.out.println(consumerName + " 消费: " + order + " (队列剩余: " + orderQueue.size() + ")");
Thread.sleep((long)(Math.random() * 1000)); // 模拟处理延迟
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
OrderProcessingSystem system = new OrderProcessingSystem();
// 启动2个生产者,3个消费者
new Thread(system.new Producer()).start();
new Thread(system.new Producer()).start();
new Thread(system.new Consumer("消费者-A")).start();
new Thread(system.new Consumer("消费者-B")).start();
new Thread(system.new Consumer("消费者-C")).start();
// 运行30秒后停止
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
}
运行效果:2个生产者持续生成订单,3个消费者并行处理,队列满时生产者自动阻塞,队列空时消费者自动等待。
7️⃣ 常见问题FAQ
Q1:如何避免死锁?
A:死锁的四必要条件——互斥、保持与等待、不可剥夺、循环等待,解决方案:
- 使用单一锁(如
BlockingQueue) - 遵循固定锁顺序(如总是先锁生产者再锁消费者)
- 使用
tryLock设置超时
Q2:生产者/消费者数量如何配置?
A:没有固定公式,一般原则:
- IO密集型任务:消费者数量可以稍多(如CPU核心数×2)
- CPU密集型任务:消费者数量≈CPU核心数
- 测试驱动:通过压测找到吞吐量最高点
Q3:如何处理消费者处理失败?
A:常见策略:
- 重试机制(最多重试3次,放入死信队列)
- 记录错误日志后丢弃
- 使用死信队列(另一个队列)暂存失败消息
Q4:BlockingQueue的put和offer有什么区别?
A:
put:阻塞等待直到成功offer:尝试插入,失败立即返回falseoffer(e, timeout, unit):超时等待
8️⃣ 总结与最佳实践建议
生产消费者模式的核心价值在于解耦生产与消费的速度差异,是构建高并发系统的基石。
最佳实践总结:
- 优先使用
BlockingQueue:减少手动同步代码,提高可靠性 - 警惕虚假唤醒:使用
while循环检查条件状态 - 合理设置队列容量:避免内存溢出(建议设置容量上限)
- 处理线程中断:捕获
InterruptedException后重置中断状态 - 监控队列深度:生产环境中监控队列长度,预防积压或空转
进阶思考:当吞吐量要求极高时,可以考虑无锁队列(如Disruptor框架)或分布式消息队列(如Kafka、RabbitMQ),但在单机场景下,Java原生的BlockingQueue家族已经足以应对95%的业务需求。
一句话总结:生产者消费者模式是并发编程的“瑞士军刀”,而
BlockingQueue是Java给开发者最优雅的礼物。