黑马程序员-JAVA-银行业务调度系统
来源:互联网 发布:login.php 编辑:程序博客网 时间:2024/05/26 12:05
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
引
传智播客中有一个面试题目项目就是这个银行业务调度系统,今天也来实现一把。
需求分析
- 这家银行有6个业务窗口,其中4个是普通窗口,1个快速窗口,1个VIP窗口,银行的客户比例为普通:快速:VIP=6:3:1,各个类型用户安排到对应窗口,快速和VIP窗口在空闲时也可以服务普通用户,用户业务办理时间是随机的,有最小值,但快速用户永远是最小值。模拟无需GUI,通过log显示结果
- 假定最小业务办理时间为30秒,最大600秒
- 快速和VIP窗口仅在普通窗口全忙时才接收普通用户
- 用户通过取号器来排队,窗口就绪后由号码排队系统分配客户
第一轮设计
- 通过一个模拟执行程序执行,执行程序 生成一个银行(6个窗口),一个用户模拟(取号),为了方便,采用100倍速模拟。
- 业务窗口有3种类型,行为上并无不同,仅仅标记的用户偏向不同,采用一个enum业务类型来标识。
- 用户有明确的业务目的,也就视为:有特定的业务办理时间(假定银行窗口业务员素质相同),有特定的业务类型(普通/快速/VIP),进入银行就有自己的排队号码(未进入设定为-1)。
- 业务类型有最小和最大办理时间,普通和VIP为(30,600),快速为(30,30)
- 银行取号系统负责给用户分配排队号码,并给空闲的窗口分配客户。
第二轮设计
- 窗口应该知道当前服务的客户,窗口应该属于银行,窗口应该有准备就绪开始,计划停止(服务完当前客户)的方法,以通知取号系统。
- 取号系统应该在没有排队用户时阻塞,取号系统应该为每个窗口尽快分配用户,按:用户采用3个阻塞队列,快速/VIP窗口轮询特有队列和普通队列。普通窗口直接阻塞取普通队列
实现
代码如下:
业务类型
/** * 银行业务分类 * @author lz * */public enum BusinessType { /** * 普通业务 耗时30秒到10分钟 */ Normal(30,600), /** * 快速业务 耗时30秒 */ Quick(30,30), /** * VIP业务 耗时30秒到10分钟 */ VIP(30,600); /** * 最小和最大耗时 */ public int minTime,maxTime; private BusinessType(int minTime, int maxTime) { this.minTime = minTime; this.maxTime = maxTime; }}
客户
/** * 银行客户 * @author lz * */public class BankUser { /** * 来办业务的类型 */ public final BusinessType biz; /** * 业务耗时 */ public final int bizTime; /** * 排队号码 */ public String queueNum; @Override public String toString() { return "BankUser [biz=" + biz + ", bizTime=" + bizTime + ", queueNum=" + queueNum + "]"; } public String shortInfo() { return queueNum+"("+bizTime+")"; } public BankUser(BusinessType biz, int bizTime) { super(); this.biz = biz; this.bizTime = bizTime; } /** * 进入银行排队 * @param bank 银行 */ public void enQueue(Bank bank){ this.queueNum=bank.offerNum(this); Logger.getLogger("BankQueue").info("新客户:"+this); }}
银行,包括窗口和调度系统
import java.time.Instant;import java.util.ArrayList;import java.util.List;import java.util.concurrent.BlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;import java.util.logging.Logger;import static cc.sisel.util.Quic.*;/** * 银行抽象,包括窗口和业务调度系统 默认构造包含6个窗口 其中一个VIP一个快速,其他普通 * * @author lz * */public class Bank implements Runnable { final BankUserHandler banksys; List<BusinessWindow> servWindows; public Bank(BankUserHandler banksys, List<BusinessWindow> servWindows) { super(); this.banksys = banksys; this.servWindows = servWindows; } public Bank() { super(); this.banksys = new BankUserHandler(); this.servWindows = new ArrayList<Bank.BusinessWindow>(6); this.servWindows.add(new BusinessWindow(BusinessType.VIP)); this.servWindows.add(new BusinessWindow(BusinessType.Quick)); this.servWindows.add(new BusinessWindow(BusinessType.Normal)); this.servWindows.add(new BusinessWindow(BusinessType.Normal)); this.servWindows.add(new BusinessWindow(BusinessType.Normal)); this.servWindows.add(new BusinessWindow(BusinessType.Normal)); } @Override public void run() { ExecutorService exes = Executors.newFixedThreadPool(servWindows.size()); for (BusinessWindow businessWindow : servWindows) { exes.execute(businessWindow); businessWindow.readyToService(); } // 演示用,控制台输出客户状态 Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate( (() -> sp("等待客户:" + this.banksys.watingSum() + " 今日客户总数:" + this.banksys.cameUserSum())), 1, 5, TimeUnit.SECONDS); } /** * 取号 * * @param bankUser * 取号的用户 * @return 号码条 */ public String offerNum(BankUser bankUser) { return this.banksys.accept(bankUser); } /** * 业务调度系统 负责分派客户到窗口 并监视银行中等待客户的队列 记录今日来访的客户计数 生成号码条 * * @author lz * */ /** * @author lz * */ public class BankUserHandler { BlockingQueue<BankUser> normalQueue = new LinkedBlockingQueue<BankUser>(), quickQueue = new LinkedBlockingQueue<BankUser>(), vipQueue = new LinkedBlockingQueue<BankUser>(); private int vipn = 0, quickn = 0, normaln = 0; public int watingSum() { return this.normalQueue.size() + this.quickQueue.size() + this.vipQueue.size(); } public int cameUserSum() { return this.normaln + this.quickn + this.vipn; } /** * 生成号码条 * * @param bankUser * 用户 * @return 号码条 */ private String accept(BankUser bankUser) { switch (bankUser.biz) { case VIP: this.vipQueue.offer(bankUser); return "V" + vipn++; case Quick: this.quickQueue.offer(bankUser); return "Q" + quickn++; case Normal: this.normalQueue.offer(bankUser); return "N" + normaln++; } return null; } /** * 为窗口分派用户,若当前没有则等待1秒 * * @param businessWindow * 待分配的窗口 * @return 客户 */ public BankUser offerUser(BusinessWindow businessWindow) { BankUser offer = null; while (offer == null) { switch (businessWindow.offerType) { case VIP: return this.offerVIP(); case Quick: return this.offerQuick(); case Normal: return this.offerNormal(); } ts(10); } return offer; } /** * 为快速窗口查找匹配的客户 * * @return 合适的客户 */ private BankUser offerQuick() { if (!this.quickQueue.isEmpty()) { try { return this.quickQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } else if (!this.normalQueue.isEmpty()) { try { return this.normalQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } return null; } /** * 为VIP窗口查找匹配的客户 * * @return 合适的客户 */ private BankUser offerVIP() { if (!this.vipQueue.isEmpty()) { try { return this.vipQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } else if (!this.normalQueue.isEmpty()) { try { return this.normalQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } return null; } /** * 为普通窗口查找匹配的客户 * 这个方法是阻塞的 * @return 合适的客户 */ private BankUser offerNormal() { try { return this.normalQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } } /** * 服务窗口 * @author lz * */ public class BusinessWindow implements Runnable { /** * 窗口序号 */ public final int serial; /** * 是否接续服务 */ private boolean keepWorking; /** * 当前服务客户 */ private BankUser currentServing; /** * 窗口服务类型 */ public final BusinessType offerType; public BusinessWindow(BusinessType offerType) { super(); this.offerType = offerType; this.serial = Bank.this.servWindows.size(); } @Override public String toString() { return "Window[" + offerType.toString().charAt(0) + serial + "]"; } public String shortInfo() { return "W[" + offerType.toString().charAt(0) + serial + "]"; } /** * 就绪,可以服务 */ public void readyToService() { this.keepWorking = true; } /** * 停止,服务完成当前用户就停止 */ public void scheduleStop() { this.keepWorking = false; } /** * @return 是否正在服务 */ public boolean isBusy() { return currentServing != null; } /** * 服务过程 * 服务完成会使当前客户离开 */ private void handle() { if (currentServing != null) { // Logger.getLogger("BankQueue").info(this.shortInfo() + // " 开始服务:" + currentServing.shortInfo() + " @" +Instant.now()); // sp(this + " 开始服务:" + currentServing + " @" +Instant.now()); ts(currentServing.bizTime * 10); Logger.getLogger("BankQueue").info(this.shortInfo() + " 服务完毕:" + currentServing.shortInfo() + " @" +Instant.now()); sp(this + " 服务完毕:" + currentServing + " @" + Instant.now()); this.currentServing = null; } } /** * 取一个用户 */ private void next() { this.currentServing = Bank.this.banksys.offerUser(this); } @Override public void run() { while (true) { if (this.keepWorking) { if(this.currentServing == null){ this.next(); } this.handle(); } else { ts(1000); } } } }}
模拟器,包括用户生成器
/** * 模拟器 * @author lz * */public class Simulator { public static void main(String[] args) { Logger.getLogger("BankQueue").setLevel(Level.ALL); Bank bank = new Bank(); new Thread(bank).start(); BankUserGenerator gen=new BankUserGenerator(); Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate( (() -> gen.randomGen().enQueue(bank)), 100, 300, TimeUnit.MILLISECONDS); }}/** * 模拟银行用户生成器 * @author lz * */class BankUserGenerator{ Random r=new Random(); BankUser randomGen(){ BusinessType biz; int rnum=r.nextInt(10); if(rnum>8){ biz=BusinessType.VIP; }else if(rnum>5){ biz=BusinessType.Quick; }else{ biz=BusinessType.Normal; } return new BankUser(biz, biz.minTime+r.nextInt(biz.maxTime-biz.minTime+1)); }}
分析
这里的快速队列和VIP队列采用了轮询的方式,效率偏低,如果可以,用标记多头阻塞队列是理想的,但是这个容器难度颇大,就没有实现。
而且客户的取号系统基本没有在分配中用上,实际应该维持一个映射,系统应该只对号码操作。
窗口的开关功能虽然实现了,但是没有模拟。
扩展讨论
一个用户在办理业务的时候可能多次往返窗口,这样重新拿号势必麻烦,而银行业务人员有时又需要传递用户,序列操作,最后,这个系统中可以加入用户等待时间统计,预估一个等待时间,提高客户体验,银行的窗口分配策略也可以动态修改就更好了。
这些需求,初步分析,可以通过内部号码生成与插队,动态代理调度系统并传递数据的方式完成,而统计就比较简单了,实现的方式很多。
0 0
- 黑马程序员-java-银行业务调度系统
- 黑马程序员-java基础 银行业务调度系统
- 黑马程序员 Java 银行业务调度系统
- 黑马程序员---银行业务调度系统【java】
- [黑马程序员]--Java语言银行业务调度系统
- 黑马程序员-java 银行业务调度系统
- 黑马程序员 java 银行业务调度系统
- 黑马程序员--java技术--银行业务调度系统
- 黑马程序员-java-银行业务调度系统
- 黑马程序员 java银行业务调度系统 随笔
- 黑马程序员 Java银行业务调度系统
- 黑马程序员-Java银行业务调度系统
- 黑马程序员--java银行业务调度系统
- 黑马程序员----Java 银行业务调度系统
- 黑马程序员-JAVA-银行业务调度系统
- 黑马程序员---银行业务调度系统
- <黑马程序员>银行业务调度系统
- 【黑马程序员】银行业务调度系统
- hadoop常用命令
- 算法导论 第二章作业
- mysql默认字符编码设置教程 my.ini设置字符编码(亲测)
- 如何成为一名软件架构师
- RFID系统信号的通信过程
- 黑马程序员-JAVA-银行业务调度系统
- Python调用C可执行程序(subprocess)
- 输入一行字符,统计其中有多少个单词,单词之间用空格分开
- SQLiteDataBase中query与rawQuery的区别
- AOJ Problem NO.492青蛙过河
- BZOJ 3221 花神游历各国 (线段树)
- Codeforces 534B Covered Path
- java 反射的基础学习
- A* A星 算法 C语言 实现代码