生产者-消费者模型是多线程编程中最经典的同步协作模型 ,核心解决:生产者生产数据→放入缓冲区→消费者从缓冲区取数据 ,实现解耦、异步、限流,避免生产速度和消费速度不匹配导致的数据丢失/阻塞。
一、核心概念
- 生产者线程:负责生产数据,放入共享缓冲区
- 消费者线程:负责从缓冲区取出数据,消费数据
- 共享缓冲区:线程安全的容器(队列/列表),是协作核心
- 同步机制:必须保证缓冲区空时消费者等待、缓冲区满时生产者等待;生产/消费后唤醒对方
二、Java 实现方式(3种常用方案)
方式1:Object wait/notify 实现(基础版)
利用 Java 内置的 wait()、notifyAll() 实现线程等待/唤醒,最基础、面试必考。
java
import java.util.LinkedList;
import java.util.Queue;
// 共享缓冲区
class SharedBuffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity; // 缓冲区最大容量
public SharedBuffer(int capacity) {
this.capacity = capacity;
}
// 生产者:放入数据
public synchronized void produce(int value) throws InterruptedException {
// 缓冲区满,生产者等待
while (queue.size() == capacity) {
System.out.println("缓冲区已满,生产者等待...");
wait();
}
queue.add(value);
System.out.println("生产者生产:" + value + ",缓冲区大小:" + queue.size());
notifyAll(); // 唤醒等待的消费者
}
// 消费者:取出数据
public synchronized void consume() throws InterruptedException {
// 缓冲区空,消费者等待
while (queue.isEmpty()) {
System.out.println("缓冲区为空,消费者等待...");
wait();
}
int value = queue.poll();
System.out.println("消费者消费:" + value + ",缓冲区大小:" + queue.size());
notifyAll(); // 唤醒等待的生产者
}
}
// 生产者线程
class Producer implements Runnable {
private final SharedBuffer buffer;
public Producer(SharedBuffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
int value = 1;
try {
while (true) { // 持续生产
buffer.produce(value++);
Thread.sleep(500); // 模拟生产耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者线程
class Consumer implements Runnable {
private final SharedBuffer buffer;
public Consumer(SharedBuffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
while (true) { // 持续消费
buffer.consume();
Thread.sleep(1000); // 模拟消费耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 测试主类
public class ProducerConsumerDemo {
public static void main(String[] args) {
SharedBuffer buffer = new SharedBuffer(3); // 缓冲区容量3
new Thread(new Producer(buffer), "生产者").start();
new Thread(new Consumer(buffer), "消费者").start();
}
}
关键知识点:
- 必须用
while循环判断缓冲区状态(防止虚假唤醒) wait()会释放锁,sleep()不会释放锁notifyAll()唤醒所有等待线程,比notify()更安全
方式2:Lock/Condition 实现(进阶版)
用 ReentrantLock + Condition 替代 synchronized,支持精准唤醒生产者/消费者,效率更高。
java
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class SharedBuffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 生产者等待条件
private final Condition notEmpty = lock.newCondition(); // 消费者等待条件
public SharedBuffer(int capacity) {
this.capacity = capacity;
}
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 生产者等待
}
queue.add(value);
System.out.println("生产:" + value + ",缓冲区:" + queue.size());
notEmpty.signal(); // 精准唤醒消费者
} finally {
lock.unlock(); // 必须手动释放锁
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 消费者等待
}
int value = queue.poll();
System.out.println("消费:" + value + ",缓冲区:" + queue.size());
notFull.signal(); // 精准唤醒生产者
} finally {
lock.unlock();
}
}
}
优势:
- 两个
Condition分离等待队列,避免无效唤醒 - 手动加锁/释放锁,灵活性更高
方式3:BlockingQueue 实现(推荐,开发首选)
Java 并发包提供的线程安全阻塞队列 (ArrayBlockingQueue/LinkedBlockingQueue),内置阻塞机制,代码极简。
java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// 生产者
class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
int value = 1;
try {
while (true) {
queue.put(value++); // 队列满则自动阻塞
System.out.println("生产:" + (value-1));
Thread.sleep(500);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
int value = queue.take(); // 队列空则自动阻塞
System.out.println("消费:" + value);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 测试
public class BlockingQueueDemo {
public static void main(String[] args) {
// 容量为3的阻塞队列
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
核心优势:
- 无需手动写同步、等待、唤醒逻辑
- 开箱即用,工业级稳定,实际开发首选
put()/take()自带阻塞,offer()/poll()非阻塞
三、三种方案对比
| 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Object wait/notify | 基础、面试必考 | 代码繁琐、易出错、效率低 | 学习、面试 |
| Lock/Condition | 精准唤醒、高效 | 代码稍复杂 | 高级并发场景 |
| BlockingQueue | 极简、安全、高效 | 封装度高,底层原理隐藏 | 生产环境、实际开发 |
四、核心作用
- 解耦:生产者和消费者不直接通信,通过缓冲区交互
- 异步:生产和消费并发执行,无需同步等待
- 削峰限流:缓冲区平衡生产/消费速度,避免系统崩溃
总结
- 生产者-消费者核心是共享缓冲区 + 线程同步
- 基础面试用
wait/notify,实际开发用BlockingQueue - 必须处理线程安全 和虚假唤醒(用while循环)
- 三大价值:解耦、异步、限流