一道面试题:餐馆模拟

前阵子遇到一个面试题,当时没有做出来,后来断断续续的用了一周的时间做了出来,但感觉也不完全对,先来看看题目,稍后再讨论。

问题

模拟一个餐馆,三个厨师,二个服务员,厨师单独做菜,2分钟一个菜,服务员单独送菜10秒一个

分析

一看这问题就知道考查的点是多线程,生产者与消费者模型的模拟类问题,《Java编程思想》中有类似的例子,但是这个问题比书中的例子要复杂一些,因为厨师和服务员都有多个,所以要考虑到厨师与服务员的管理(资源管理),以及定单的队列,因为当厨师都在忙的时候,定单必须要放入队列中。另外的最难的地方就在于如何写测试以证明程序是正常工作,这是最难的地方。关键的点应该是:

  1. 厨师的管理:包括安排厨师去做菜,厨师做好菜后要通知服务员去送菜,当没有厨师时,或没有菜单时都要空闲等待
  2. 服务员的管理:安排服务员去送菜,当没有菜时或没有服务员时都要等待
  3. 定单的管理:当没有厨师去做菜时,要把定单放入队列里,等待厨师,这个可以让厨师管理者一同管理;同理当没有服务员时也要放入队列,这个可以由服务员管理者来管理
  4. 测试用例:用一定数量的定单数来验证程序是正确的。

测试

可以先想一些测试用例:

  1. 当只有一个定单时,很容易,应该是做菜时间加上送菜时间就好了,同时只用一个厨师和一个服务员
  2. 当有二个时,因为有三个厨师和二个服务员,二道菜应该同时完成,因为资源充足,没有等待
  3. 当有三个时,厨师不用等,但服务员要等,所以前二个同时完成,第三个要再等一个送菜时间
  4. 当有四个时,当厨师在做第四道菜时,第三个已经送完,所以第四个菜要多等一个做菜时间和送菜时间
  5. 第五个应该与第四个同时完成
  6. 第六个比第五个多等一个送菜时间
用这个图来表达会更清楚各个菜的完成时间:

菜单完成时序列图

实现

测试代码,也许这个测试代码应该重构,以去掉重复的,但是那样容易出错,这样虽然重复,但很直观。

package com.effectivejava.threading.basics;

import junit.framework.TestCase;

public class ResturauntTest extends TestCase {
    private Resturaunt resturaunt;
    /*
     * TODO:
     * time to cook: 200 ms
     * time to deliver: 50 ms
     * tolerance is 100 ms
     * If tolerance is less than 100, cases will fail.
     * These three parameters are important for these tests to pass, if you change time2cook or time2deliver,
     * cases would fail.
     */
    private int tolerance = 100; // in ms
    public ResturauntTest() {
        super("ResturauntTest");
    }
    
    @Override
    public void setUp() {
        resturaunt = new Resturaunt("Hilton's Place");
    }
    
    @Override
    public void tearDown() {
        resturaunt.closeDoor();
    }
    
    public void testOneOrder() throws InterruptedException {
        System.out.println("============ start testing one orders =============");
        final Order order[] = createOrders(1);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(order[0] + " should be ready", order[0].isReady());
    }
    
    public void testTwoOrders() throws InterruptedException {
        System.out.println("============ start testing two orders =============");
        final Order orders[] = createOrders(2);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[0] + " hsould is ready", orders[0].isReady());
        assertTrue(orders[1] + " is ready", orders[1].isReady());
    }
    
    public void testThreeOrders() throws InterruptedException {
        System.out.println("============ start testing three orders =============");
        final Order orders[] = createOrders(3);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[0] + " is ready", orders[0].isReady());
        assertTrue(orders[1] + " is ready", orders[1].isReady());
        
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[2] + " is ready", orders[2].isReady());
    }
    
    public void testFourOrders() throws InterruptedException {
        System.out.println("============== start testing four orders ===============");
        final Order orders[] = createOrders(4);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[0] + " should be ready", orders[0].isReady());
        assertTrue(orders[1] + " should be ready too", orders[1].isReady());
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[2] + " should be ready now", orders[2].isReady());
        
        timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        Thread.sleep(timeToWait);
        assertTrue(orders[3] + " is ready yet", orders[3].isReady());
    }
    
    public void testFiveOrders() throws InterruptedException {
        System.out.println("======================start testing five orders=====================");
        final Order orders[] = createOrders(5);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[0] + " should be ready", orders[0].isReady());
        assertTrue(orders[1] + " should be ready too", orders[1].isReady());
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[2] + " should be ready now", orders[2].isReady());
        
        timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        Thread.sleep(timeToWait);
        assertTrue(orders[3] + " is ready yet", orders[3].isReady());
        assertTrue(orders[4] + " is ready", orders[4].isReady());
    }
    
    public void testSixOrders() throws InterruptedException {
        System.out.println("======================start testing six orders=====================");
        final Order orders[] = createOrders(6);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[0] + " should be ready", orders[0].isReady());
        assertTrue(orders[1] + " should be ready too", orders[1].isReady());
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[2] + " should be ready now", orders[2].isReady());
        
        timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        Thread.sleep(timeToWait);
        assertTrue(orders[3] + " is ready yet", orders[3].isReady());
        assertTrue(orders[4] + " is ready", orders[4].isReady());
        
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[5] + " is ready", orders[5].isReady());
    }

    public void testSevenOrders() throws InterruptedException {
        System.out.println("======================start testing seven orders=====================");
        final Order orders[] = createOrders(7);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[0] + " should be ready", orders[0].isReady());
        assertTrue(orders[1] + " should be ready too", orders[1].isReady());
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[2] + " should be ready now", orders[2].isReady());
        
        timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        Thread.sleep(timeToWait);
        assertTrue(orders[3] + " is ready yet", orders[3].isReady());
        assertTrue(orders[4] + " is ready", orders[4].isReady());
        
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[5] + " is ready", orders[5].isReady());
        
        timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        Thread.sleep(timeToWait);
        assertTrue(orders[6] + " is ready", orders[6].isReady());
    }
    
    public void testEightOrders() throws InterruptedException {
        System.out.println("======================start testing eight orders=====================");
        final Order orders[] = createOrders(8);
        int timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[0] + " should be ready", orders[0].isReady());
        assertTrue(orders[1] + " should be ready too", orders[1].isReady());
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[2] + " should be ready now", orders[2].isReady());
        
        timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        Thread.sleep(timeToWait);
        assertTrue(orders[3] + " is ready yet", orders[3].isReady());
        assertTrue(orders[4] + " is ready", orders[4].isReady());
        
        timeToWait = tolerance;
        timeToWait += WaitorManager.TIME_TO_DELIVER;
        Thread.sleep(timeToWait);
        assertTrue(orders[5] + " is ready", orders[5].isReady());
        
        timeToWait = tolerance;
        timeToWait += CookManager.TIME_TO_COOK;
        Thread.sleep(timeToWait);
        assertTrue(orders[6] + " is ready", orders[6].isReady());
        assertTrue(orders[7] + " is ready", orders[7].isReady());
    }
    
    private Order[] createOrders(int count) {
        Order orders[] = new Order[count];
        for (int i = 0; i < count; i++) {
            final Order o = new Order("Pizza " + "#" + i);
            resturaunt.submitOrder(o);
            assertFalse(o + " not ready", o.isReady());
            orders[i] = o;
        }
        return orders;
    }
}

需要一个Resturant类,它仅接收定单,然后委派给CookManager和WaitorManager去做事,自己并不做事

package com.effectivejava.threading.basics;


/*
 * From this demo, we learn the lesson that why TDD is so good in development practice.
 * Without TDD, you are not assured that your programs work correctly.
 * TDD can, at least, assure you that your programs work.
 */
public class Resturaunt {
    private String name;
    private int cookLimit;
    private int waitorLimit;
    private CookManager cookManager;
    private WaitorManager waitorManager;
    
    public Resturaunt(String name) {
        this.name = name;
        cookLimit = 3;
        waitorLimit = 2;
        System.out.println("Resturation: " + name + " is open now, Welcome everyone!");
        cookManager = new CookManager(this, cookLimit);
        waitorManager = new WaitorManager(waitorLimit);
    }
    
    public void submitOrder(Order o) {
        cookManager.prepareFood(o);
    }
    
    /*
     * There is a problem: waitor #1 always do more work than waitor #2.
     * Because food delivery is quick to finish, so when food is preparing, waitor #1 finishes its
     * delivery and back to queue. When food is prepared, it is waitor #1 to deliver because it is
     * on the head of the queue.
     * Solution:
     * When waitor or cook is about to deliver or cook, remove it from the queue and push it onto
     * the queue after it finishes.
     */
    public void foodPrepared(Order o) {
        waitorManager.deliverFood(o);
    }
    
    public void closeDoor() {
        cookManager.knockOff();
        waitorManager.knockOff();
        System.out.println("Resturant: " + name + " is closed now, welcome to come tomorrow!");
    }
}

接下来是CookManager:它要负责调度厨师们来工作,给他们分配任务,并管理定单,当没厨师时或没定单时都要空闲等待,同时还要监听,当菜做完后通知WaitorManager来送菜。因为厨师只由CookManager来管理,并不直接与外界沟通,所以Cook类可以做为CookManager的内部类
package com.effectivejava.threading.basics;


import java.util.ArrayList;
import java.util.List;


public class CookManager implements Runnable {
    public static final int TIME_TO_COOK = 200;
    private boolean knockedOff;
    private List<Cook> availableCooks;
    private Resturaunt resturaunt;
    private int cookLimit;
    private List<Order> ordersToPrepare;
    
    public CookManager(Resturaunt r, int cl) {
        knockedOff = false;
        resturaunt = r;
        cookLimit = cl;
        availableCooks = new ArrayList<Cook>(cookLimit);
        availableCooks.add(new Cook("Cook #1", this));
        availableCooks.add(new Cook("Cook #2", this));
        availableCooks.add(new Cook("Cook #3", this));
        ordersToPrepare = new ArrayList<Order>();
        new Thread(this).start();
    }
    
    public void prepareFood(Order o) {
        synchronized (ordersToPrepare) {
            ordersToPrepare.add(o);
            ordersToPrepare.notify();
        }
    }
    
    public void foodPrepared(Cook shef, Order o) {
        synchronized (availableCooks) {
            availableCooks.add(shef);
            availableCooks.notify();
        }
        resturaunt.foodPrepared(o);
    }
    
    public void run() {
        System.out.println("Cook manager start working...");
        while (!knockedOff) {
            synchronized (ordersToPrepare) {
                while (ordersToPrepare.size() < 1) {
                    try {
                        ordersToPrepare.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("OrdersToPrepare: " + ordersToPrepare);
                Cook shef = null;
                for (Cook cook : availableCooks) {
                    if (!cook.isCooking()) {
                        cook.prepareFood(ordersToPrepare.remove(0));
                        shef = cook;
                        break;
                    }
                }
                synchronized (availableCooks) {
                    availableCooks.remove(shef);
                }
            }
            
            synchronized (availableCooks) {
                while (availableCooks.size() < 1) {
                    try {
                        availableCooks.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        System.out.println("Cook manager knocked off");
    }
    
    public void knockOff() {
        for (Cook c : availableCooks) {
            c.knockOff();
        }
        knockedOff = true;
    }
    
    private class Cook implements Runnable {
    	private String name;
    	private boolean knockedOff;
    	private Order order;
    	private CookManager manager;
    	public Cook(String n, CookManager m) {
    		name = n;
    		manager = m;
    		order = null;
    		new Thread(this).start();
    	}
    	
    	public void prepareFood(Order o) {
    		synchronized (this) {
    			System.out.println(this + "Start Cooking " + o);
    			order = o;
    			notify();
    		}
    	}
    	
    	public void run() {
    		while (!knockedOff) {
    			synchronized (this) {
    				while (order == null) {
    					try {
    						wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    			}
    			System.out.println(this + "Cooking " + order);
    			try {
    				Thread.sleep(CookManager.TIME_TO_COOK);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println(this + "Food " + order + " is cooked, ready to deliver");
    			final Order o = order;
    			synchronized (this) {
    				order = null;
    			}
    			manager.foodPrepared(this, o);
    		}
    	}
    	
    	public void knockOff() {
    		knockedOff = true;;
    	}
    	
    	public boolean isCooking() {
    		return order != null;
    	}
    	
    	@Override
    	public String toString() {
    		return name + " ";
    	}
    }
}

WaitorManager也是一样,管理待送的菜和调度Waitor
package com.effectivejava.threading.basics;


import java.util.ArrayList;
import java.util.List;


public class WaitorManager implements Runnable {
    public static final int TIME_TO_DELIVER = 50;
    private boolean knockedOff;
    private int waitorLimit;
    private List<Waitor> availableWaitors;
    private List<Order> ordersToDeliver;
    
    public WaitorManager(int wl) {
        knockedOff = false;
        waitorLimit = wl;
        availableWaitors = new ArrayList<Waitor>(waitorLimit);
        availableWaitors.add(new Waitor("Waitor #1", this));
        availableWaitors.add(new Waitor("Waitor #2", this));
        ordersToDeliver = new ArrayList<Order>();
        new Thread(this).start();
    }
    
    public void deliverFood(Order o) {
        synchronized (ordersToDeliver) {
            ordersToDeliver.add(o);
            ordersToDeliver.notify();
        }
    }
    
    public void foodDelivered(Waitor w) {
        synchronized (availableWaitors) {
            availableWaitors.add(w);
            availableWaitors.notify();
        }
    }
    
    public void run() {
        System.out.println("Waitor manager start working...");
        while (!knockedOff) {
            synchronized (ordersToDeliver) {
                while (ordersToDeliver.size() < 1) {
                    try {
                        ordersToDeliver.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                
                System.out.println("ordertodeliver: " + ordersToDeliver);
                Waitor w = null;
                for (Waitor waitor : availableWaitors) {
                    if (!waitor.isDelivering()) {
                        waitor.deliverFood(ordersToDeliver.remove(0));
                        w = waitor;
                        break;
                    }
                }
                
                synchronized (availableWaitors) {
                    availableWaitors.remove(w);
                }
            }
            
            synchronized (availableWaitors) {
                while (availableWaitors.size() < 1) {
                    try {
                        availableWaitors.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        System.out.println("Waitor manager knocked off");
    }
    
    public void knockOff() {
        for (Waitor w : availableWaitors) {
            w.knockOff();
        }
        knockedOff = true;
    }
    
    private class Waitor implements Runnable {
    	private String name;
    	private Order order;
    	private boolean knockedOff;
    	private WaitorManager manager;
    	
    	public Waitor(String n, WaitorManager m) {
    		name = n;
    		order = null;
    		manager = m;
    		new Thread(this).start();
    	}
    	
    	public void deliverFood(Order o) {
    		synchronized (this) {
    			System.out.println(this + "Start delivering " + o);
    			order = o;
    			notify();
    		}
    	}
    	
    	public void run() {
    		while (!knockedOff) {
    			synchronized (this) {
    				while (order == null) {
    					try {
    						wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    			}
    			System.out.println(this + " delivering " + order);
    			try {
    				Thread.sleep(WaitorManager.TIME_TO_DELIVER);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println(this + "Food " + order + " is delivered, food is ready");
    			synchronized (this) {
    				order.ready();
    				order = null;
    			}
    			manager.foodDelivered(this);
    		}
    	}
    	
    	public void knockOff() {
    		knockedOff = true;
    	}
    	
    	public boolean isDelivering() {
    		synchronized (this) {
    			return order != null;
    		}
    	}
    	
    	@Override
    	public String toString() {
    		return name + " ";
    	}
    }
}

最后是数据类型Order类,仅是一个数据
package com.effectivejava.threading.basics;


public class Order {
    private boolean ready;
    private String name;
    
    public Order(String n) {
        name = n;
        ready = false;
    }
    public boolean isReady() {
        return ready;
    }
    
    public void ready() {
        ready = true;
    }
    
    @Override
    public String toString() {
        return name;
    }
}

总结

这个问题说难不难,但是要想写的正确真不是一件容易的事,也不可能在一二个小时内解决,前前后后花了我近一周的时间才能写到现在的程度,但现在我还不敢保证正确。当然,也许牛人或者高手能很快就解决,所说明我还需要更多的努力。但至少可以证明一点:有关多线程的问题很难编写和调试。对于这个例子来说,虽然预期一和二同时完成,但很难确定它们的顺序,因为线程的运行是不确定的。对于这个例子如果把tolerance改小一个些,或者把TIME_TO_COOK,TIME_TO_DELIVER改大一些,就会有Case失败。

拓展

1. N个厨师,M个服务员,基于我的程序来改,一点不难,还是难在测试上面,如何保证它是正确的。
2. 服务员还有收拾桌子的任务
3. 厨师做菜的时间不固定,服务员送的时间也不固定,这样才更加真实。
真实的生活是很难模拟的,但是一个套优秀的系统和管理方式确实能大大的提高效率,就好比一些小餐馆一旦人过多了,即使只一会儿,也会让餐馆陷入混乱:菜上的慢,客人等的急,没人收拾桌子,菜做错和送错等;但是对于大餐馆来说却总能应付的来,即使是像婚庆或者酒会。
水平浅,真心希望能有牛人来实现一个版本让我参考。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值