Java实现排队论的原理

引入:

前段时间去银行办业务,排队的人那是真多,自己正式办理业务也就不到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实现排队论的原理详细介绍,希望对大家的学习有所帮助。

(0)

相关推荐

  • 模拟打印机排队打印效果

    package com.cooly; import java.util.LinkedList; /** * @author coolyqq *模拟打印打印机排队打印 *分发类 */ public class DataDistribute { private static DataDistribute instance = null; private final static byte[] obj = new byte[0];//锁机制 private LinkedList<DataDistrib

  • php+ajax实现带进度条的大数据排队导出思路以及源码

    废话不多说,先上效果图: 点击导出,实现 点击导出 统计完成之后 点击确定 下面来谈谈实现的思路: 前面导出操作简单,从第二个导出操作开始: 点击"确定"调用exportCsv函数 复制代码 代码如下: <a class="on" href="javascript:exportCsv();"><em>导出</em></a> exportCvs函数如下function exportCsv(){ //清

  • Java实现排队论的原理

    引入: 前段时间去银行办业务,排队的人那是真多,自己正式办理业务也就不到5分钟,但是却足足等了两个小时(相信很多人都遇到过这种情况),对这种服务水平真的是无语了,但是问题又来了,银行应该开几个窗口,既能保证整体的服务质量,又能保证资源资源的利用率呢?下面我们就通过排队论来模拟这个问题. 排队论简介 排队论是研究系统随机聚散现象和随机系统工作工程的数学理论和方法,又称随机服务系统理论,为运筹学的一个分支.我们下面对排队论做下简化处理,先看下图: 我们在图的左侧安排若干个蓝色服务台,右侧为可能会过来

  • 深入了解Java GC的工作原理

    JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代下载地址  和旧生代采用不同的垃圾回收机制. 首先来看一下JVM内存结构,它是由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示. JVM学习笔记 JVM内存管理和JVM垃圾回收 JVM内存组成结构 JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示: 1)堆 所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制.堆

  • Java多线程定时器Timer原理及实现

    前言 定时/计划功能在Java应用的各个领域都使用得非常多,比方说Web层面,可能一个项目要定时采集话单.定时更新某些缓存.定时清理一批不活跃用户等等.定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程方式进行处理,所以它和多线程技术关联还是相当大的.那和ThreadLocal一样,还是先讲原理再讲使用,Timer的实现原理不难,就简单扫一下就好了. Timer的schedule(TimeTask task, Date time)的使用 该方法的作用是在执行的日期执行一

  • Java设计模式之装饰模式原理与用法实例详解

    本文实例讲述了Java设计模式之装饰模式原理与用法.分享给大家供大家参考,具体如下: 装饰模式能在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象.JDK中IO的设计就用到了装饰模式,通过过滤流对节点流进行包装来实现功能的扩展. 装饰模式的角色的组成: ① 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加工功能的对象.(InputStream.OutputStream) ② 具体构件(Concrete Co

  • Java 读写锁实现原理浅析

    最近做的一个小项目中有这样的需求:整个项目有一份config.json保存着项目的一些配置,是存储在本地文件的一个资源,并且应用中存在读写(读>>写)更新问题.既然读写并发操作,那么就涉及到操作互斥,这里自然想到了读写锁,本文对读写锁方面的知识做个梳理. 为什么需要读写锁? 与传统锁不同的是读写锁的规则是可以共享读,但只能一个写,总结起来为:读读不互斥,读写互斥,写写互斥,而一般的独占锁是:读读互斥,读写互斥,写写互斥,而场景中往往读远远大于写,读写锁就是为了这种优化而创建出来的一种机制. 注

  • Java方法重载Overload原理及使用解析

    这篇文章主要介绍了Java方法重载Overload原理及使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 为什么要用方法重载: 对于功能类似的方法来说,因为参数列表不一样,如果定义不同名称的方法,太麻烦且难以记忆. 为了解决这个问题,引入方法的重载. 重载的定义: 多个方法的名称一样,但参数列表不一样. 不使用方法重载 定义三个功能类似的方法 public class TestOverload { public static int su

  • Java字节缓冲流原理与用法详解

    本文实例讲述了Java字节缓冲流原理与用法.分享给大家供大家参考,具体如下: 一 介绍 BufferInputStresm和BufferOutputStream 这两个流类为IO提供了带缓冲区的操作,一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能. 二 各类中方法比较 从应用程序中把输入放入文件,相当于将一缸水倒入另外一个缸中: FileOutputStream的write方法:相当于一滴一滴地把水"转移过去. DataOutputStream的writeXXX方法:

  • Java图形界面Swing原理及用法解析

    这篇文章主要介绍了Java图形界面Swing原理及用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JButton组件 布局管理器 FlowLayout 流式布局 BorderLayout 方位布局 GridLayout 表格布局 绝对布局 JLable 组件 文本框组件 JPanel轻量级容器 创建事件监听类 (更换监听类实现监听) 窗口监听适配器 都可使用匿名类实现监听 每个监听方法都可以返回一个Event对象来返回监听值 以上就是本

  • Java局部变量线程安全原理分析

    这篇文章主要介绍了Java局部变量线程安全原理分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 方法调用栈结构: 每个线程都有自己独立的方法调用栈: 这种局部变量不共享,从而保证线程安全的技术,称为线程封闭技术. 案例:数据库连接池.采用线程封闭技术,线程获取的数据库连接connection,是独立的,在这个线程在关闭获取的这个connection之前,不会再分配给其他线程. 思考:递归调用太深,可能导致栈溢出. 栈溢出原因: 因为每调用一个

  • Java中synchronized实现原理详解

    记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字"同步",也成为了我们解决多线程情况的百试不爽的良药.但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它. 诚然,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么

随机推荐