吃货餐厅的故事:揭秘生产者-消费者模型

95 阅读6分钟

让我们用一家"疯狂吃货餐厅"的故事,结合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();

​问题爆发:​

  1. ​CPU空转浪费​​:当传送带满/空时,线程陷入死循环空转
  2. ​数据不同步​​:多个吃货可能同时吃一个汉堡(线程不安全)
  3. ​死锁风险​​:厨师等空间,吃货等汉堡,互相卡死
Copy
👨‍🍳 制作汉堡!当前汉堡:5
😋 吃掉汉堡!当前汉堡:4
😋 吃掉汉堡!当前汉堡:3  <- 正常情况
😋 吃掉汉堡!当前汉堡:2  <- 突然吃货减少但汉堡还在
😋 吃掉汉堡!当前汉堡:-1!  <- 灾难!汉堡变负数!

🛠️ 第二章:老板的救星 - 锁和通知机制

老板引入了两件神器:

  1. synchronized锁 - 餐厅门禁卡(保证同一时间只有一个操作)
  2. 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

🔑 核心机制解析:

  1. ​门禁卡机制(synchronized)​​:

    java
    Copy
    synchronized (lock) {
        // 临界区代码
    }
    

    保证同一时间只有一个厨师或吃货操作传送带

  2. ​服务员对讲机(wait/notify)​​:

    • lock.wait():当前线程释放锁并进入等待状态
    • lock.notifyAll():唤醒所有等待这个锁的线程
  3. ​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?

  1. ​避免手动同步​​:内置线程安全机制
  2. ​多种等待策略​​:超时设置、立即返回等
  3. ​丰富的方法​​:批量操作、查看队列状态等
  4. ​高性能实现​​: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
... // 完美协调,永不超限

🧠 生产者-消费者模型核心思想

  1. ​解耦生产与消费​​:

image.png

生产者专注生产,消费者专注消费,缓冲区作为中介
  1. ​平衡负载能力​​:

    • 生产速度 > 消费速度:缓冲区满时生产者等待
    • 消费速度 > 生产速度:缓冲区空时消费者等待
  2. ​应对突发流量​​:

    java
    Copy
    // 突发大量订单
    conveyorBelt.put(burger); // 缓冲区吸收冲击
    

📱 Android实战应用场景

  1. ​图片加载框架​​:

    java
    Copy
    // 生产者:网络请求线程
    // 缓冲区:图片缓存队列
    // 消费者:UI渲染线程
    
  2. ​日志记录系统​​:

    java
    Copy
    // 生产者:各业务线程产生日志
    // 缓冲区:日志队列
    // 消费者:日志写入线程
    
  3. ​数据同步模块​​:

    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开发中遇到多线程协作问题,记得想想厨师👨‍🍳、传送带🍔和吃货😋的配合方式!