案例介绍:厨师与传菜员的餐厅协作模拟
这个案例通过一个简化的模拟程序,展示了餐厅中厨师与传菜员之间的协作过程。该程序使用多线程机制,以类比真实世界中厨师烹饪菜品并放入窗口、传菜员从窗口取走菜品并送至餐桌的场景。
场景背景
在餐厅的日常运营中,厨师在后厨负责烹饪菜品,并将制作好的菜品放在一个共享的窗口(或称为传菜口)。传菜员负责从窗口取走菜品并传送到餐桌,以供顾客享用。这一过程需要厨师和传菜员之间的高效配合:厨师需要在窗口有空间时上菜,而传菜员需要及时取走菜品,避免窗口因菜品堆积而阻塞。
程序结构
-
主类 (ChefAndWaiterExample):负责启动两个线程,一个模拟厨师,另一个模拟传菜员。两者通过共享的 Window 对象进行交互。
-
窗口类 (Window):代表餐厅中的传菜窗口,作为共享资源,用于存放菜品。它通过一个队列来模拟菜品的堆放,并设置了最大容量,避免窗口被菜品堵塞。
-
厨师类 (Chef):负责烹饪并将菜品放入窗口。每次厨师制作好一道菜时,会检查窗口是否有空间可放入新菜品。如果窗口已满,厨师将等待传菜员取走菜品后再继续上菜。
-
传菜员类 (Waiter):负责从窗口中取走菜品。传菜员的职责是及时取走菜品,保证窗口不会因为菜品堆积而导致厨师无法继续上菜。如果窗口为空,传菜员将等待厨师放入新菜品。
练习用代码1
移除了缓冲区里生产和消耗的逻辑
import java.util.LinkedList;
import java.util.Queue;
public class ChefAndWaiterExample {
public static void main(String[] args) {
// 创建一个共享的窗口,用于厨师放置菜品和传菜员取走菜品
Window window = new Window();
// 创建并启动厨师线程,命名为“厨师”
Thread chefThread = new Thread(new Chef(window), "厨师");
chefThread.start();
// 创建并启动传菜员线程,命名为“传菜员”
Thread waiterThread = new Thread(new Waiter(window), "传菜员");
waiterThread.start();
}
}
// 共享的窗口类,模拟餐厅中厨师和传菜员之间的工作交接点
class Window {
// 使用队列来模拟菜品的存放,队列中存放的是菜品名称
private final Queue<String> dishQueue = new LinkedList<>();
// 最大容量,即窗口中最多可以放置的菜品数量
private static final int MAX_CAPACITY = 5;
// 厨师上菜的方法,将菜品放入窗口
public synchronized void placeDish(String dish) throws InterruptedException {
}
// 传菜员取菜的方法,从窗口中取走菜品
public synchronized String takeDish() throws InterruptedException {
}
}
// 厨师类,代表负责烹饪并将菜品放入窗口的线程
class Chef implements Runnable {
private final Window window; // 引用共享的窗口对象
// 构造函数,接受一个窗口对象
public Chef(Window window) {
this.window = window;
}
// 厨师线程的执行逻辑
public void run() {
int count = 0; // 用于跟踪已生产的菜品数量
int maxCount = 10; // 定义厨师总共要做的菜品数量
try {
// 定义一些菜品的名称,厨师将随机选择一种进行制作
String[] dishes = {"牛排", "沙拉", "意大利面", "寿司", "汤"};
while (count <= maxCount) {
// 随机选择一种菜品并放入窗口
window.placeDish(dishes[(int) (Math.random() * dishes.length)]);
count++; // 增加已制作菜品的数量
Thread.sleep(1000); // 模拟制作菜品所需的时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 传菜员类,代表负责从窗口中取走菜品并送到餐桌的线程
class Waiter implements Runnable {
private final Window window; // 引用共享的窗口对象
// 构造函数,接受一个窗口对象
public Waiter(Window window) {
this.window = window;
}
// 传菜员线程的执行逻辑
public void run() {
try {
// 传菜员总共需要取走 10 道菜
for (int i = 0; i < 10; i++) {
window.takeDish(); // 从窗口中取走一道菜品
Thread.sleep(1500); // 模拟传菜到餐桌所需的时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
练习用代码2
删除了run
方法里的逻辑
import java.util.LinkedList;
import java.util.Queue;
public class ChefAndWaiterExample {
public static void main(String[] args) {
// 创建一个共享的窗口,用于厨师放置菜品和传菜员取走菜品
Window window = new Window();
// 创建并启动厨师线程,命名为“厨师”
Thread chefThread = new Thread(new Chef(window), "厨师");
chefThread.start();
// 创建并启动传菜员线程,命名为“传菜员”
Thread waiterThread = new Thread(new Waiter(window), "传菜员");
waiterThread.start();
}
}
// 共享的窗口类,模拟餐厅中厨师和传菜员之间的工作交接点
class Window {
// 使用队列来模拟菜品的存放,队列中存放的是菜品名称
private final Queue<String> dishQueue = new LinkedList<>();
// 最大容量,即窗口中最多可以放置的菜品数量
private static final int MAX_CAPACITY = 5;
// 厨师上菜的方法,将菜品放入窗口
public synchronized void placeDish(String dish) throws InterruptedException {
// 如果窗口中的菜品已达到最大容量,等待传菜员取走菜品
if (dishQueue.size() >= MAX_CAPACITY) {
wait(); // 释放锁并等待,直到传菜员取走菜品
}
// 厨师将菜品放入窗口并打印消息
System.out.println(Thread.currentThread().getName() + "上了一道菜:" + dish);
dishQueue.add(dish); // 将菜品加入队列中
notify(); // 通知可能正在等待的传菜员线程
}
// 传菜员取菜的方法,从窗口中取走菜品
public synchronized String takeDish() throws InterruptedException {
// 如果窗口中没有菜品,传菜员等待厨师上菜
if (dishQueue.isEmpty()) {
wait(); // 释放锁并等待,直到厨师放置新的菜品
}
// 传菜员从窗口中取走菜品并打印消息
String dish = dishQueue.poll(); // 从队列中取出菜品
System.out.println(Thread.currentThread().getName() + "取走了一道菜:" + dish);
notify(); // 通知可能正在等待的厨师线程
return dish;
}
}
// 厨师类,代表负责烹饪并将菜品放入窗口的线程
class Chef implements Runnable {
private final Window window; // 引用共享的窗口对象
// 构造函数,接受一个窗口对象
public Chef(Window window) {
this.window = window;
}
// 厨师线程的执行逻辑
public void run() {
}
}
// 传菜员类,代表负责从窗口中取走菜品并送到餐桌的线程
class Waiter implements Runnable {
private final Window window; // 引用共享的窗口对象
// 构造函数,接受一个窗口对象
public Waiter(Window window) {
this.window = window;
}
// 传菜员线程的执行逻辑
public void run() {
}
}
代码
import java.util.LinkedList;
import java.util.Queue;
public class ChefAndWaiterExample {
public static void main(String[] args) {
// 创建一个共享的窗口,用于厨师放置菜品和传菜员取走菜品
Window window = new Window();
// 创建并启动厨师线程,命名为“厨师”
Thread chefThread = new Thread(new Chef(window), "厨师");
chefThread.start();
// 创建并启动传菜员线程,命名为“传菜员”
Thread waiterThread = new Thread(new Waiter(window), "传菜员");
waiterThread.start();
}
}
// 共享的窗口类,模拟餐厅中厨师和传菜员之间的工作交接点
class Window {
// 使用队列来模拟菜品的存放,队列中存放的是菜品名称
private final Queue<String> dishQueue = new LinkedList<>();
// 最大容量,即窗口中最多可以放置的菜品数量
private static final int MAX_CAPACITY = 5;
// 厨师上菜的方法,将菜品放入窗口
public synchronized void placeDish(String dish) throws InterruptedException {
// 如果窗口中的菜品已达到最大容量,等待传菜员取走菜品
if (dishQueue.size() >= MAX_CAPACITY) {
wait(); // 释放锁并等待,直到传菜员取走菜品
}
// 厨师将菜品放入窗口并打印消息
System.out.println(Thread.currentThread().getName() + "上了一道菜:" + dish);
dishQueue.add(dish); // 将菜品加入队列中
notify(); // 通知可能正在等待的传菜员线程
}
// 传菜员取菜的方法,从窗口中取走菜品
public synchronized String takeDish() throws InterruptedException {
// 如果窗口中没有菜品,传菜员等待厨师上菜
if (dishQueue.isEmpty()) {
wait(); // 释放锁并等待,直到厨师放置新的菜品
}
// 传菜员从窗口中取走菜品并打印消息
String dish = dishQueue.poll(); // 从队列中取出菜品
System.out.println(Thread.currentThread().getName() + "取走了一道菜:" + dish);
notify(); // 通知可能正在等待的厨师线程
return dish;
}
}
// 厨师类,代表负责烹饪并将菜品放入窗口的线程
class Chef implements Runnable {
private final Window window; // 引用共享的窗口对象
// 构造函数,接受一个窗口对象
public Chef(Window window) {
this.window = window;
}
// 厨师线程的执行逻辑
public void run() {
int count = 0; // 用于跟踪已生产的菜品数量
int maxCount = 10; // 定义厨师总共要做的菜品数量
try {
// 定义一些菜品的名称,厨师将随机选择一种进行制作
String[] dishes = {"牛排", "沙拉", "意大利面", "寿司", "汤"};
while (count <= maxCount) {
// 随机选择一种菜品并放入窗口
window.placeDish(dishes[(int) (Math.random() * dishes.length)]);
count++; // 增加已制作菜品的数量
Thread.sleep(1000); // 模拟制作菜品所需的时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 传菜员类,代表负责从窗口中取走菜品并送到餐桌的线程
class Waiter implements Runnable {
private final Window window; // 引用共享的窗口对象
// 构造函数,接受一个窗口对象
public Waiter(Window window) {
this.window = window;
}
// 传菜员线程的执行逻辑
public void run() {
try {
// 传菜员总共需要取走 10 道菜
for (int i = 0; i < 10; i++) {
window.takeDish(); // 从窗口中取走一道菜品
Thread.sleep(1500); // 模拟传菜到餐桌所需的时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}