Java實現(xiàn)排隊論的原理
引入:
前段時間去銀行辦業(yè)務,排隊的人那是真多,自己正式辦理業(yè)務也就不到5分鐘,但是卻足足等了兩個小時(相信很多人都遇到過這種情況),對這種服務水平真的是無語了,但是問題又來了,銀行應該開幾個窗口,既能保證整體的服務質(zhì)量,又能保證資源資源的利用率呢?下面我們就通過排隊論來模擬這個問題。
排隊論簡介
排隊論是研究系統(tǒng)隨機聚散現(xiàn)象和隨機系統(tǒng)工作工程的數(shù)學理論和方法,又稱隨機服務系統(tǒng)理論,為運籌學的一個分支。我們下面對排隊論做下簡化處理,先看下圖:
我們在圖的左側安排若干個藍色服務臺,右側為可能會過來的紅色顧客,中間為黃色的等候區(qū),如果有服務臺處于空閑狀態(tài),顧客可以直接去接受服務,否則就要在黃色區(qū)域等候,顧客服務的順序采用先到現(xiàn)服務的原則,現(xiàn)在如果我們知道顧客過來的概率分布,那么我們在左側安排幾個服務臺既能達到更好的服務水平,又能保證服務臺的使用率?下面我們就構建模型來模擬這個問題。
排隊論分步實現(xiàn)
1)對于排隊論,我們首先要確定顧客屬性,知道顧客什么時候到達,需要的服務耗時等,我們首先創(chuàng)建一個顧客類,在這里我們指定了顧客服務的最大、最小時間,這里我們?yōu)榱撕喕椭苯诱J為服務時間完全隨機:
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)上面我們定義了顧客,緊接著就需要定義一個排隊隊列,我們先看下隊列的屬性,這里我們定義一個數(shù)組,用它來保存排隊的顧客,定義下一個顧客到來的最小、最大時間間隔以及顧客來不來的概率(這里簡單說明下,如果下一個顧客的間隔時間是3,但是通過概率計算并為滿足,則這個顧客不進入隊列,這樣設置的原因是盡可能的使顧客達到有很大的隨機性)和隊列中最大的排隊人數(shù)。
public class CustomerQuene { //等待顧客隊列 private LinkedList<CustomerBean> customers = new LinkedList<CustomerBean>(); //下一個顧客過來最短時間 private int minTime = 0; //下一個顧客過來最大時間 private int maxTime = 1 * 1000; //來顧客的概率 private double rate = 0.9; //標識是否繼續(xù)產(chǎn)生顧客 private boolean flag = true; //最大排隊人數(shù) private int maxWaitNum = 0; }
3)顧客和排隊的隊列都有了,我們就設置一個產(chǎn)生顧客的線程,讓它不斷的產(chǎn)生顧客,這里就有我們上面說的時間和概率分布。
/** *@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)顧客相關的屬性和方法都已經(jīng)準備好,下面就設置下服務臺相關的屬性,這里我們直接把服務臺設置成線程,定義一些服務指標,如服務的顧客數(shù)目、總等待時間、總服務時間、最大等待時間等。
public class ServantThread extends Thread{ //服務顧客數(shù)目 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(); //如果顧客線程已經(jīng)關閉且隊列中沒有顧客,服務臺線程關閉釋放 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("系統(tǒng)運行時間:" + (b -a) + "ms"); System.out.println("系統(tǒng)空閑時間:" + ((b -a) * servantNum - ServantThread.getSumServeTime())); ServantThread.print(); try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } } } }
運行結果
1)運行開始
2)顧客產(chǎn)生線程關閉
3)最后服務水平
通過修改服務臺的個數(shù)就可以評估在當前的顧客情況下應該設置幾個服務臺。
完整代碼
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; //標識是否繼續(xù)產(chǎn)生顧客 private boolean flag = true; //最大排隊人數(shù) 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: 獲取等待顧客數(shù)量 */ 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("顧客產(chǎn)生線程"); 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{ //服務顧客數(shù)目 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(); //如果顧客線程已經(jīng)關閉且隊列中沒有顧客,服務臺線程關閉釋放 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("服務顧客數(shù)目:" + customerNum); System.out.println("最大等待時間:" + maxWaitTime); System.out.println("等待顧客數(shù)目:" + CustomerQuene.getCustomerQuene().getWaitCustomerNum()); System.out.println("最大等待顧客數(shù)目:" + CustomerQuene.getCustomerQuene().getMaxWaitNum()); //輸出顧客平均等待時間,保留兩位小數(shù) 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("系統(tǒng)總服務時間:" + 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("系統(tǒng)運行時間:" + (b -a) + "ms"); System.out.println("系統(tǒng)空閑時間:" + ((b -a) * servantNum - ServantThread.getSumServeTime())); ServantThread.print(); try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } } } }
以上就是關于Java實現(xiàn)排隊論的原理詳細介紹,希望對大家的學習有所幫助。
相關文章
microlog4android將Android Log日志寫到SD卡文件中實現(xiàn)方法
這篇文章主要介紹了microlog4android將Android Log日志寫到SD卡文件中實現(xiàn)方法的相關資料,需要的朋友可以參考下2016-10-10使用Java如何對復雜的數(shù)據(jù)類型排序和比大小
我相信大家在第一次接觸算法的時候,最先接觸的肯定也是從排序算法開始的,下面這篇文章主要給大家介紹了關于使用Java如何對復雜的數(shù)據(jù)類型排序和比大小的相關資料,需要的朋友可以參考下2023-12-12詳解java8在Collection中新增加的方法removeIf
這篇文章主要介紹了詳解java8在Collection中新增加的方法removeIf的相關資料,需要的朋友可以參考下2018-01-01Java?hibernate延遲加載get和load的區(qū)別
這篇文章主要介紹了Java?hibernate延遲加載get和load的區(qū)別,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09springboot+mysql+mybatis實現(xiàn)控制臺打印sql
在Spring Boot中使用MyBatis與MySQL,并希望在控制臺打印SQL語句,可以通過配置MyBatis的日志級別來實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-01-01