让我们用一家"疯狂吃货餐厅"的故事,结合Java代码,彻底讲透生产者-消费者模型!你将看到多线程如何像餐厅后厨和前厅一样高效协作!
🍽️ 故事背景:疯狂吃货餐厅
Copy
厨师(生产者)👨🍳 --> 传送带(缓冲区)🍽️ --> 吃货(消费者)😋
在这家餐厅:
- 厨师:不停制作美味汉堡(生产者线程)
- 传送带:最多只能放5个汉堡(有界缓冲区)
- 吃货:疯狂吃掉汉堡(消费者线程)
核心问题:如何让厨师和吃货协调工作,既不让传送带空转,也不让汉堡堆积溢出?
🧑🍳 第一章:灾难现场(无协调版)
java
Copy
class Kitchen {
private int burgerCount = 0; // 传送带上的汉堡数量
private final int MAX_BURGERS = 5; // 传送带容量
// 厨师做汉堡
public void makeBurger() {
while (burgerCount >= MAX_BURGERS) {
// 传送带满了怎么办?干等着!
}
burgerCount++;
System.out.println("👨🍳 制作汉堡!当前汉堡:" + burgerCount);
}
// 吃货吃汉堡
public void eatBurger() {
while (burgerCount <= 0) {
// 没汉堡了怎么办?干等着!
}
burgerCount--;
System.out.println("😋 吃掉汉堡!当前汉堡:" + burgerCount);
}
}
💥 运行结果:灾难现场!
厨师线程:
java
Copy
new Thread(() -> {
while (true) kitchen.makeBurger();
}).start();
吃货线程:
java
Copy
new Thread(() -> {
while (true) kitchen.eatBurger();
}).start();
问题爆发:
- CPU空转浪费:当传送带满/空时,线程陷入死循环空转
- 数据不同步:多个吃货可能同时吃一个汉堡(线程不安全)
- 死锁风险:厨师等空间,吃货等汉堡,互相卡死
Copy
👨🍳 制作汉堡!当前汉堡:5
😋 吃掉汉堡!当前汉堡:4
😋 吃掉汉堡!当前汉堡:3 <- 正常情况
😋 吃掉汉堡!当前汉堡:2 <- 突然吃货减少但汉堡还在
😋 吃掉汉堡!当前汉堡:-1! <- 灾难!汉堡变负数!
🛠️ 第二章:老板的救星 - 锁和通知机制
老板引入了两件神器:
synchronized锁 - 餐厅门禁卡(保证同一时间只有一个操作)wait()/notify()- 服务员对讲机(协调通知)
java
Copy
class Kitchen {
private int burgerCount = 0;
private final int MAX_BURGERS = 5;
private final Object lock = new Object(); // 门禁卡
public void makeBurger() throws InterruptedException {
synchronized (lock) {
while (burgerCount >= MAX_BURGERS) {
System.out.println("🚫 传送带满了,厨师休息");
lock.wait(); // 交出锁,进入等待区
}
burgerCount++;
System.out.println("👨🍳 制作汉堡!当前汉堡:" + burgerCount);
lock.notifyAll(); // 通知吃货们:有汉堡啦!
}
}
public void eatBurger() throws InterruptedException {
synchronized (lock) {
while (burgerCount <= 0) {
System.out.println("🆘 没汉堡了,吃货等待");
lock.wait(); // 交出锁,进入等待区
}
burgerCount--;
System.out.println("😋 吃掉汉堡!当前汉堡:" + burgerCount);
lock.notifyAll(); // 通知厨师们:有空间啦!
}
}
}
✅ 运行效果:完美协作!
Copy
👨🍳 制作汉堡!当前汉堡:1
😋 吃掉汉堡!当前汉堡:0
👨🍳 制作汉堡!当前汉堡:1
👨🍳 制作汉堡!当前汉堡:2
👨🍳 制作汉堡!当前汉堡:3
🆘 没汉堡了,吃货等待 <- 吃货发现没汉堡就等待
👨🍳 制作汉堡!当前汉堡:1 <- 厨师被唤醒
😋 吃掉汉堡!当前汉堡:0
🔑 核心机制解析:
-
门禁卡机制(synchronized):
java Copy synchronized (lock) { // 临界区代码 }保证同一时间只有一个厨师或吃货操作传送带
-
服务员对讲机(wait/notify):
lock.wait():当前线程释放锁并进入等待状态lock.notifyAll():唤醒所有等待这个锁的线程
-
while循环检测:
java Copy while (burgerCount >= MAX_BURGERS) { lock.wait(); }防止"虚假唤醒"(线程可能无缘无故被唤醒)
🚀 第三章:工业化升级 - 阻塞队列(BlockingQueue)
老板发现每次都要手动管理太麻烦,于是购买了"智能传送带系统":
java
Copy
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
class AdvancedKitchen {
private final BlockingQueue<Integer> conveyorBelt =
new ArrayBlockingQueue<>(5); // 容量为5的传送带
// 厨师制作汉堡
public void makeBurger() throws InterruptedException {
conveyorBelt.put(1); // 放汉堡,如果满了自动等待
System.out.println("👨🍳 制作汉堡!当前汉堡:" + conveyorBelt.size());
}
// 吃货吃汉堡
public void eatBurger() throws InterruptedException {
conveyorBelt.take(); // 拿汉堡,如果没有自动等待
System.out.println("😋 吃掉汉堡!当前汉堡:" + conveyorBelt.size());
}
}
🌟 BlockingQueue 的魔法方法:
| 方法 | 行为 | 等效故事动作 |
|---|---|---|
put() | 队列满时阻塞等待 | 厨师在传送带满时休息 |
take() | 队列空时阻塞等待 | 吃货在传送带空时等待 |
offer() | 队列满时返回false而不阻塞 | 厨师看传送带满就离开 |
poll() | 队列空时返回null而不阻塞 | 吃货看没汉堡就离开 |
💡 为什么推荐BlockingQueue?
- 避免手动同步:内置线程安全机制
- 多种等待策略:超时设置、立即返回等
- 丰富的方法:批量操作、查看队列状态等
- 高性能实现:Java优化过的并发容器
🧪 第四章:多厨师多吃货的并发世界
现实中餐厅有多个厨师和吃货:
java
Copy
public class Restaurant {
public static void main(String[] args) {
AdvancedKitchen kitchen = new AdvancedKitchen();
// 3个厨师线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while (true) {
try {
kitchen.makeBurger();
Thread.sleep(100); // 做汉堡需要时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "厨师-" + (i+1)).start();
}
// 5个吃货线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
while (true) {
try {
kitchen.eatBurger();
Thread.sleep(200); // 吃汉堡需要时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "吃货-" + (i+1)).start();
}
}
}
📊 运行结果分析:
Copy
厨师-1 👨🍳 制作汉堡!当前汉堡:1
厨师-2 👨🍳 制作汉堡!当前汉堡:2
吃货-3 😋 吃掉汉堡!当前汉堡:1
吃货-1 😋 吃掉汉堡!当前汉堡:0
厨师-3 👨🍳 制作汉堡!当前汉堡:1
吃货-2 😋 吃掉汉堡!当前汉堡:0
... // 完美协调,永不超限
🧠 生产者-消费者模型核心思想
- 解耦生产与消费:
生产者专注生产,消费者专注消费,缓冲区作为中介
-
平衡负载能力:
- 生产速度 > 消费速度:缓冲区满时生产者等待
- 消费速度 > 生产速度:缓冲区空时消费者等待
-
应对突发流量:
java Copy // 突发大量订单 conveyorBelt.put(burger); // 缓冲区吸收冲击
📱 Android实战应用场景
-
图片加载框架:
java Copy // 生产者:网络请求线程 // 缓冲区:图片缓存队列 // 消费者:UI渲染线程 -
日志记录系统:
java Copy // 生产者:各业务线程产生日志 // 缓冲区:日志队列 // 消费者:日志写入线程 -
数据同步模块:
java Copy // 生产者:网络数据获取 // 缓冲区:待处理数据队列 // 消费者:数据库写入线程
💎 终极总结:生产者-消费者三要素
| 要素 | 实现方式 | 餐厅类比 | 代码示例 |
|---|---|---|---|
| 生产者 | 创建数据的线程 | 厨师 | new Thread(() -> produce()) |
| 消费者 | 处理数据的线程 | 吃货 | new Thread(() -> consume()) |
| 缓冲区 | 协调两者的队列 | 传送带 | BlockingQueue |
| 协调机制 | 阻塞操作/wait-notify | 服务员对讲机 | put()/take() 或 wait()/notify() |
记住这个万能公式:
java
Copy
// 生产者伪代码
while (true) {
Item item = produceItem(); // 生产数据
buffer.put(item); // 放入缓冲区
}
// 消费者伪代码
while (true) {
Item item = buffer.take(); // 从缓冲区取数据
consume(item); // 消费数据
}
通过这个餐厅故事,你已经掌握了生产者-消费者模型的精髓!下次在Android开发中遇到多线程协作问题,记得想想厨师👨🍳、传送带🍔和吃货😋的配合方式!