Java实现排队论的原理
作者:xiaojimanman
引入:
前段时间去银行办业务,排队的人那是真多,自己正式办理业务也就不到5分钟,但是却足足等了两个小时(相信很多人都遇到过这种情况),对这种服务水平真的是无语了,但是问题又来了,银行应该开几个窗口,既能保证整体的服务质量,又能保证资源资源的利用率呢?下面我们就通过排队论来模拟这个问题。
排队论简介
排队论是研究系统随机聚散现象和随机系统工作工程的数学理论和方法,又称随机服务系统理论,为运筹学的一个分支。我们下面对排队论做下简化处理,先看下图:
我们在图的左侧安排若干个蓝色服务台,右侧为可能会过来的红色顾客,中间为黄色的等候区,如果有服务台处于空闲状态,顾客可以直接去接受服务,否则就要在黄色区域等候,顾客服务的顺序采用先到现服务的原则,现在如果我们知道顾客过来的概率分布,那么我们在左侧安排几个服务台既能达到更好的服务水平,又能保证服务台的使用率?下面我们就构建模型来模拟这个问题。
排队论分步实现
1)对于排队论,我们首先要确定顾客属性,知道顾客什么时候到达,需要的服务耗时等,我们首先创建一个顾客类,在这里我们指定了顾客服务的最大、最小时间,这里我们为了简化就直接认为服务时间完全随机:
public class CustomerBean { //最小服务时间 private static int minServeTime = 3 * 1000; //最大服务时间 private static int maxServeTime = 15 * 1000; //顾客达到时间 private long arriveTime; //顾客需要服务耗时 private int serveTime; public CustomerBean() { //设置到达时间 arriveTime = System.currentTimeMillis(); //随机设置顾客的服务时间 serveTime = (int) (Math.random() * (maxServeTime - minServeTime) + minServeTime); } }
2)上面我们定义了顾客,紧接着就需要定义一个排队队列,我们先看下队列的属性,这里我们定义一个数组,用它来保存排队的顾客,定义下一个顾客到来的最小、最大时间间隔以及顾客来不来的概率(这里简单说明下,如果下一个顾客的间隔时间是3,但是通过概率计算并为满足,则这个顾客不进入队列,这样设置的原因是尽可能的使顾客达到有很大的随机性)和队列中最大的排队人数。
public class CustomerQuene { //等待顾客队列 private LinkedList<CustomerBean> customers = new LinkedList<CustomerBean>(); //下一个顾客过来最短时间 private int minTime = 0; //下一个顾客过来最大时间 private int maxTime = 1 * 1000; //来顾客的概率 private double rate = 0.9; //标识是否继续产生顾客 private boolean flag = true; //最大排队人数 private int maxWaitNum = 0; }
3)顾客和排队的队列都有了,我们就设置一个产生顾客的线程,让它不断的产生顾客,这里就有我们上面说的时间和概率分布。
/** *@Description: 生成顾客线程 *@Version:1.1.0 */ private class CustomerThread extends Thread { private CustomerThread(String name) { super(name); } @Override public void run() { while (flag) { //队尾添加一个新顾客 if (Math.random() < rate) { customers.addLast(new CustomerBean()); if (maxWaitNum < customers.size()) { maxWaitNum = customers.size(); } } int sleepTime = (int) (Math.random() * (maxTime - minTime) + minTime); try { TimeUnit.MILLISECONDS.sleep(sleepTime); } catch (Exception e) { e.printStackTrace(); } } } }
4)如果队列中有顾客排队切有空闲的服务台,就需要获取队头的顾客去接受服务
public synchronized CustomerBean getCustomerBean() { if (customers == null || customers.size() < 1) { return null; } return customers.removeFirst(); }
5)顾客相关的属性和方法都已经准备好,下面就设置下服务台相关的属性,这里我们直接把服务台设置成线程,定义一些服务指标,如服务的顾客数目、总等待时间、总服务时间、最大等待时间等。
public class ServantThread extends Thread{ //服务顾客数目 private static int customerNum = 0; //总等待时间 private static int sumWaitTime = 0; //总服务时间 private static int sumServeTime = 0; //最大等待时间 private static int maxWaitTime = 0; private boolean flag = false; private String name; }
6)服务台最主要的工作就是服务顾客,这里我们把服务顾客相关的操作写到线程的run方法中。
public void run() { flag = true; while (flag) { CustomerBean customer = CustomerQuene.getCustomerQuene().getCustomerBean(); //如果顾客线程已经关闭且队列中没有顾客,服务台线程关闭释放 if (customer == null) { if (!CustomerQuene.getCustomerQuene().isFlag()) { flag = false; print(); } continue; } long now = System.currentTimeMillis(); int waitTime = (int) (now - customer.getArriveTime()); //保存最大的等待时间 if (waitTime > maxWaitTime) { maxWaitTime = waitTime; } //睡眠时间为顾客的服务时间,代表这段时间在服务顾客 try { TimeUnit.MILLISECONDS.sleep(customer.getServeTime()); } catch (Exception e) { e.printStackTrace(); } System.err.println(name + " 服务顾客耗时:" + customer.getServeTime() + "ms\t顾客等待:" + waitTime + "ms"); customerNum++; sumWaitTime += waitTime; sumServeTime += customer.getServeTime(); } }
7)最后我们编写一个测试模型,来验证服务水平
/** *@Description: */ package com.lulei.opsearch.quene; import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) { //开门 System.out.println("开门接客啦!"); boolean flag = true; CustomerQuene.getCustomerQuene(); long a = System.currentTimeMillis(); int servantNum = 10; for (int i = 0; i < servantNum; i++) { ServantThread thread = new ServantThread("服务台" + i); thread.start(); } while (flag) { long b = System.currentTimeMillis(); if (b - a > 1 * 60 * 1000 && flag) { //关门 flag = false; CustomerQuene.getCustomerQuene().close(); System.out.println("关门不接客啦!"); } System.out.println("系统运行时间:" + (b -a) + "ms"); System.out.println("系统空闲时间:" + ((b -a) * servantNum - ServantThread.getSumServeTime())); ServantThread.print(); try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } } } }
运行结果
1)运行开始
2)顾客产生线程关闭
3)最后服务水平
通过修改服务台的个数就可以评估在当前的顾客情况下应该设置几个服务台。
完整代码
1)顾客类
/** *@Description: */ package com.lulei.opsearch.quene; public class CustomerBean { //最小服务时间 private static int minServeTime = 3 * 1000; //最大服务时间 private static int maxServeTime = 15 * 1000; //顾客达到时间 private long arriveTime; //顾客需要服务耗时 private int serveTime; public CustomerBean() { //设置到达时间 arriveTime = System.currentTimeMillis(); //随机设置顾客的服务时间 serveTime = (int) (Math.random() * (maxServeTime - minServeTime) + minServeTime); } public static int getMinServeTime() { return minServeTime; } public static void setMinServeTime(int minServeTime) { CustomerBean.minServeTime = minServeTime; } public static int getMaxServeTime() { return maxServeTime; } public static void setMaxServeTime(int maxServeTime) { CustomerBean.maxServeTime = maxServeTime; } public long getArriveTime() { return arriveTime; } public void setArriveTime(long arriveTime) { this.arriveTime = arriveTime; } public int getServeTime() { return serveTime; } public void setServeTime(int serveTime) { this.serveTime = serveTime; } }
2)顾客队列
/** *@Description: */ package com.lulei.opsearch.quene; import java.util.LinkedList; import java.util.concurrent.TimeUnit; public class CustomerQuene { //等待顾客队列 private LinkedList<CustomerBean> customers = new LinkedList<CustomerBean>(); //下一个顾客过来最短时间 private int minTime = 0; //下一个顾客过来最大时间 private int maxTime = 1 * 1000; //来顾客的概率 private double rate = 0.9; //标识是否继续产生顾客 private boolean flag = true; //最大排队人数 private int maxWaitNum = 0; public int getMaxWaitNum() { return maxWaitNum; } public boolean isFlag() { return flag; } /** * @return * @Author:lulei * @Description: 获取排在队头的顾客 */ public synchronized CustomerBean getCustomerBean() { if (customers == null || customers.size() < 1) { return null; } return customers.removeFirst(); } public void close() { if (flag) { flag = false; } } /** * @return * @Author:lulei * @Description: 获取等待顾客数量 */ public int getWaitCustomerNum() { return customers.size(); } /** *@Description: 生成顾客线程 *@Version:1.1.0 */ private class CustomerThread extends Thread { private CustomerThread(String name) { super(name); } @Override public void run() { while (flag) { //队尾添加一个新顾客 if (Math.random() < rate) { customers.addLast(new CustomerBean()); if (maxWaitNum < customers.size()) { maxWaitNum = customers.size(); } } int sleepTime = (int) (Math.random() * (maxTime - minTime) + minTime); try { TimeUnit.MILLISECONDS.sleep(sleepTime); } catch (Exception e) { e.printStackTrace(); } } } } //单例模式开始 private static class CustomerQueneDao { private static CustomerQuene customerQuene = new CustomerQuene(); } private CustomerQuene() { CustomerThread customerThread = new CustomerThread("顾客产生线程"); customerThread.start(); } public static CustomerQuene getCustomerQuene() { return CustomerQueneDao.customerQuene; } //单例模式结束 public int getMinTime() { return minTime; } public void setMinTime(int minTime) { this.minTime = minTime; } public int getMaxTime() { return maxTime; } public void setMaxTime(int maxTime) { this.maxTime = maxTime; } public double getRate() { return rate; } public void setRate(double rate) { this.rate = rate; } }
3)服务台线程
/** *@Description: */ package com.lulei.opsearch.quene; import java.util.concurrent.TimeUnit; import com.lulei.util.ParseUtil; public class ServantThread extends Thread{ //服务顾客数目 private static int customerNum = 0; //总等待时间 private static int sumWaitTime = 0; //总服务时间 private static int sumServeTime = 0; //最大等待时间 private static int maxWaitTime = 0; private boolean flag = false; private String name; public ServantThread(String name) { super(name); this.name = name; } public static int getMaxWaitTime() { return maxWaitTime; } public static int getSumServeTime() { return sumServeTime; } @Override public void run() { flag = true; while (flag) { CustomerBean customer = CustomerQuene.getCustomerQuene().getCustomerBean(); //如果顾客线程已经关闭且队列中没有顾客,服务台线程关闭释放 if (customer == null) { if (!CustomerQuene.getCustomerQuene().isFlag()) { flag = false; print(); } continue; } long now = System.currentTimeMillis(); int waitTime = (int) (now - customer.getArriveTime()); //保存最大的等待时间 if (waitTime > maxWaitTime) { maxWaitTime = waitTime; } //睡眠时间为顾客的服务时间,代表这段时间在服务顾客 try { TimeUnit.MILLISECONDS.sleep(customer.getServeTime()); } catch (Exception e) { e.printStackTrace(); } System.err.println(name + " 服务顾客耗时:" + customer.getServeTime() + "ms\t顾客等待:" + waitTime + "ms"); customerNum++; sumWaitTime += waitTime; sumServeTime += customer.getServeTime(); } } public static void print() { if (customerNum > 0) { System.out.println("--------------------------------------"); System.out.println("服务顾客数目:" + customerNum); System.out.println("最大等待时间:" + maxWaitTime); System.out.println("等待顾客数目:" + CustomerQuene.getCustomerQuene().getWaitCustomerNum()); System.out.println("最大等待顾客数目:" + CustomerQuene.getCustomerQuene().getMaxWaitNum()); //输出顾客平均等待时间,保留两位小数 System.out.println("顾客平均等待时间:" + ParseUtil.parseDoubleToDouble((sumWaitTime * 1.0 / customerNum), 2) + "ms"); System.out.println("顾客平均服务时间:" + ParseUtil.parseDoubleToDouble((sumServeTime * 1.0 / customerNum), 2) + "ms"); System.out.println("系统总服务时间:" + sumServeTime + "ms"); } } }
4)测试模型
/** *@Description: */ package com.lulei.opsearch.quene; import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) { //开门 System.out.println("开门接客啦!"); boolean flag = true; CustomerQuene.getCustomerQuene(); long a = System.currentTimeMillis(); int servantNum = 10; for (int i = 0; i < servantNum; i++) { ServantThread thread = new ServantThread("服务台" + i); thread.start(); } while (flag) { long b = System.currentTimeMillis(); if (b - a > 1 * 60 * 1000 && flag) { //关门 flag = false; CustomerQuene.getCustomerQuene().close(); System.out.println("关门不接客啦!"); } System.out.println("系统运行时间:" + (b -a) + "ms"); System.out.println("系统空闲时间:" + ((b -a) * servantNum - ServantThread.getSumServeTime())); ServantThread.print(); try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } } } }
以上就是关于Java实现排队论的原理详细介绍,希望对大家的学习有所帮助。