想象一家忙碌的餐厅:厨师在后厨不断制作菜品(生产者),服务员将做好的菜品端给顾客(消费者),而传菜窗口就是两者之间的缓冲区。这种分工协作的方式,正是生产者-消费者模式在现实中的完美体现。
在软件开发中,生产者-消费者模式通过解耦数据生产者和消费者,实现了系统吞吐量和响应速度的平衡。就像餐厅里厨师和服务员各司其职,不会因为顾客点单速度变化而手忙脚乱。
关键点:生产者只关心"做什么菜",消费者只关心"端什么菜",两者通过缓冲区交互,互不阻塞
缓冲区相当于餐厅的传菜窗口,其实现方式直接影响系统性能:
java复制// 基于数组的环形缓冲区实现
public class CircularBuffer {
private final Object[] items;
private int putIndex;
private int takeIndex;
private int count;
public CircularBuffer(int capacity) {
this.items = new Object[capacity];
}
public synchronized void put(Object item) throws InterruptedException {
while (count == items.length) {
wait(); // 缓冲区满时等待
}
items[putIndex] = item;
putIndex = (putIndex + 1) % items.length;
count++;
notifyAll(); // 唤醒可能等待的消费者
}
public synchronized Object take() throws InterruptedException {
while (count == 0) {
wait(); // 缓冲区空时等待
}
Object item = items[takeIndex];
takeIndex = (takeIndex + 1) % items.length;
count--;
notifyAll(); // 唤醒可能等待的生产者
return item;
}
}
缓冲区容量需要根据业务特点确定:
如同餐厅需要协调厨师和服务员的工作节奏,线程间协作有几种常见方式:
| 协作方式 | 适用场景 | 实现复杂度 | 性能特点 |
|---|---|---|---|
| wait/notify | 传统Java同步 | 中等 | 上下文切换开销较大 |
| Lock/Condition | 需要精细控制时 | 较高 | 更灵活的等待条件 |
| BlockingQueue | 大多数标准场景 | 低 | JDK优化过的实现 |
| Disruptor | 超高吞吐量要求 | 高 | 无锁设计,性能极致 |
就像餐厅服务员一次端多个菜品更高效,批量处理能显著减少线程切换:
python复制# 生产者批量提交示例
def producer(batch_size=10):
batch = []
while True:
item = generate_item()
batch.append(item)
if len(batch) >= batch_size:
buffer.put_batch(batch) # 批量提交
batch = []
实测数据对比(单次 vs 批量处理):
| 批量大小 | QPS(请求/秒) | CPU利用率 |
|---|---|---|
| 1 | 12,000 | 65% |
| 10 | 38,000 | 72% |
| 50 | 52,000 | 85% |
当消费者处理速度跟不上时,需要像餐厅在高峰期限制接单一样实施背压:
常见死锁模式:
java复制// 错误示例:嵌套锁容易导致死锁
public void transfer(Account from, Account to, int amount) {
synchronized(from) {
synchronized(to) { // 可能产生循环等待
from.withdraw(amount);
to.deposit(amount);
}
}
}
解决方案:
典型症状:消费者线程意外终止,导致缓冲区堆积。排查步骤:
如同高档餐厅的流水线:
mermaid复制graph LR
冷盘厨师 --> 传菜台1 --> 摆盘师 --> 传菜台2 --> 服务员
对应代码结构:
java复制ExecutorService[] stages = new ExecutorService[3];
BlockingQueue[] queues = new BlockingQueue[2];
// 初始化各阶段工作线程
stages[0] = Executors.newFixedThreadPool(4); // 冷盘制作
stages[1] = Executors.newSingleThreadPool(); // 艺术摆盘
stages[2] = Executors.newFixedThreadPool(2); // 上菜服务
// 连接各阶段队列
queues[0] = new LinkedBlockingQueue(50); // 生食队列
queues[1] = new ArrayBlockingQueue(20); // 成品队列
现代解决方案如Project Reactor提供了更优雅的背压处理:
java复制Flux.range(1, 100)
.parallel() // 并行生产
.runOn(Schedulers.parallel())
.doOnNext(item -> {
// 模拟耗时处理
try { Thread.sleep(10); }
catch (InterruptedException e) {}
})
.sequential()
.subscribe(new BaseSubscriber<Integer>() {
@Override
protected void hookOnSubscribe(Subscription s) {
request(10); // 初始请求量
}
@Override
protected void hookOnNext(Integer value) {
// 处理完成后请求下一个
request(1);
}
});
根据业务场景选择合适实现:
是否需要严格顺序?
生产消费速率比如何?
数据敏感性如何?
延迟要求?
在实际项目中,我通常会先用ArrayBlockingQueue实现基础版本,通过压测发现瓶颈后再针对性优化。过早优化往往会导致复杂度提升而收益有限。