首先,为什么要做这个案例?一方面是我想给大家介绍一下在多线程中wait、notify、notifyAll的简单用法和一些注意事项,另一个方面是我自己也想研究这种餐厅排队系统的实现原理。这个案例仅代表我个人的一些比较的简单想法,具体的更复杂业务并未涉及。但最基本的几个业务还是要有的。这个案例实现的几个简单的基本业务如下:
一、餐厅座位管理
餐厅的座位是可以变化的,在这里我给了一个变量seatNumber可以动态修改餐厅座位数量。当然,在客户就餐后我们需要记录的是客户就餐当时的已使用座位数useNumber,以便于后期通过剩余座位号来实现叫号逻辑。
二、用户手动取号
用户手动取号,用户具体是如何取号。这里我并不关心,取号的方式有多种方式,微信公众号取号,小程序取号等等。主要是后台实现用户取号的过程。
我在这里是通过一个线程代表一个客户,以线程的方式绑定共享的排队号码容器,这个可以使用ThreadLocal类来实现。当然也可以使用Map容器storage来绑定。在这里,我使用的是后者。假设有这么一个容器,我们的客户和他取到的号码就能绑定在一起。那么取号过程中,也需要一个可以一直递增的号码takeNumber,后续取号将一直使用。
三、系统自动叫号
系统自动叫号,需要两个变量,最新叫号curCallNumber,也可以说是当前叫号。上次叫号prevCallNumber。叫号过程中会结合座位剩余和这两个变量。
四、餐厅收桌维护
餐厅运营过程中,座位是会重复清理干净给新客户使用,这个过程也要加入到整个系统实现,以维持整个排队容器正常使用。
当然还有用户延号问题等等,这里我就不一一给大家分析。接下来我们主要来实现上面的几个基本业务。
代码实现如下:
主要几个业务方法都在Counter类下,为了线程安全,我在每个业务方法上加synchronized关键字。
而且wait、notify、notifyAll都需要在加锁的前提下使用。
/**
* @author 小吉
* @description 简单自动排队实现
* @date 2020/7/5
*/
public class Counter {
private int takeNumber;//取号
private int curCallNumber;//最新叫号
private int prevCallNumber;//上次叫号
private int useNumber;//餐厅已使用座位数
private int seatNumber;//餐厅总座位数
private Map<String,Integer> storage;//排队号码
public Counter(int seatNumber){
this.seatNumber = seatNumber;
this.storage = new HashMap<>();
}
/**
* 取号
*/
public synchronized void takeNumber(){
String name = Thread.currentThread().getName();
try {
//模拟用户取号
storage.put(name,++takeNumber);
System.out.println("您好," + name + "!取号成功,您的号码是" + takeNumber + ",当前叫号是" + curCallNumber + ",前面还有" + (takeNumber - curCallNumber - 1) + "桌");
//当前用户手持号码大于最新叫号则需要等待
while (storage.get(name) > curCallNumber){
//模拟用户等待时间过长时获取进度(wait超时机制)
wait((new Random().nextInt(60) + 1) * 1000);
if(storage.get(name) > curCallNumber){
getCurrentProgress