黑马程序员-JAVA-银行业务调度系统

2024-01-02 19:08

本文主要是介绍黑马程序员-JAVA-银行业务调度系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-


传智播客中有一个面试题目项目就是这个银行业务调度系统,今天也来实现一把。

需求分析

  1. 这家银行有6个业务窗口,其中4个是普通窗口,1个快速窗口,1个VIP窗口,银行的客户比例为普通:快速:VIP=6:3:1,各个类型用户安排到对应窗口,快速和VIP窗口在空闲时也可以服务普通用户,用户业务办理时间是随机的,有最小值,但快速用户永远是最小值。模拟无需GUI,通过log显示结果
  2. 假定最小业务办理时间为30秒,最大600秒
  3. 快速和VIP窗口仅在普通窗口全忙时才接收普通用户
  4. 用户通过取号器来排队,窗口就绪后由号码排队系统分配客户

第一轮设计

  1. 通过一个模拟执行程序执行,执行程序 生成一个银行(6个窗口),一个用户模拟(取号),为了方便,采用100倍速模拟。
  2. 业务窗口有3种类型,行为上并无不同,仅仅标记的用户偏向不同,采用一个enum业务类型来标识。
  3. 用户有明确的业务目的,也就视为:有特定的业务办理时间(假定银行窗口业务员素质相同),有特定的业务类型(普通/快速/VIP),进入银行就有自己的排队号码(未进入设定为-1)。
  4. 业务类型有最小和最大办理时间,普通和VIP为(30,600),快速为(30,30)
  5. 银行取号系统负责给用户分配排队号码,并给空闲的窗口分配客户。

第二轮设计

  1. 窗口应该知道当前服务的客户,窗口应该属于银行,窗口应该有准备就绪开始,计划停止(服务完当前客户)的方法,以通知取号系统。
  2. 取号系统应该在没有排队用户时阻塞,取号系统应该为每个窗口尽快分配用户,按:用户采用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;@Overridepublic 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));}@Overridepublic 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();}@Overridepublic 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);}@Overridepublic 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队列采用了轮询的方式,效率偏低,如果可以,用标记多头阻塞队列是理想的,但是这个容器难度颇大,就没有实现。
而且客户的取号系统基本没有在分配中用上,实际应该维持一个映射,系统应该只对号码操作。
窗口的开关功能虽然实现了,但是没有模拟。

扩展讨论

一个用户在办理业务的时候可能多次往返窗口,这样重新拿号势必麻烦,而银行业务人员有时又需要传递用户,序列操作,最后,这个系统中可以加入用户等待时间统计,预估一个等待时间,提高客户体验,银行的窗口分配策略也可以动态修改就更好了。
这些需求,初步分析,可以通过内部号码生成与插队,动态代理调度系统并传递数据的方式完成,而统计就比较简单了,实现的方式很多。

这篇关于黑马程序员-JAVA-银行业务调度系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/563380

相关文章

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Windows系统宽带限制如何解除?

《Windows系统宽带限制如何解除?》有不少用户反映电脑网速慢得情况,可能是宽带速度被限制的原因,只需解除限制即可,具体该如何操作呢?本文就跟大家一起来看看Windows系统解除网络限制的操作方法吧... 有不少用户反映电脑网速慢得情况,可能是宽带速度被限制的原因,只需解除限制即可,具体该如何操作呢?本文

CentOS和Ubuntu系统使用shell脚本创建用户和设置密码

《CentOS和Ubuntu系统使用shell脚本创建用户和设置密码》在Linux系统中,你可以使用useradd命令来创建新用户,使用echo和chpasswd命令来设置密码,本文写了一个shell... 在linux系统中,你可以使用useradd命令来创建新用户,使用echo和chpasswd命令来设

电脑找不到mfc90u.dll文件怎么办? 系统报错mfc90u.dll丢失修复的5种方案

《电脑找不到mfc90u.dll文件怎么办?系统报错mfc90u.dll丢失修复的5种方案》在我们日常使用电脑的过程中,可能会遇到一些软件或系统错误,其中之一就是mfc90u.dll丢失,那么,mf... 在大部分情况下出现我们运行或安装软件,游戏出现提示丢失某些DLL文件或OCX文件的原因可能是原始安装包

电脑显示mfc100u.dll丢失怎么办?系统报错mfc90u.dll丢失5种修复方案

《电脑显示mfc100u.dll丢失怎么办?系统报错mfc90u.dll丢失5种修复方案》最近有不少兄弟反映,电脑突然弹出“mfc100u.dll已加载,但找不到入口点”的错误提示,导致一些程序无法正... 在计算机使用过程中,我们经常会遇到一些错误提示,其中最常见的就是“找不到指定的模块”或“缺少某个DL

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格