银行业务调度系统
需求:
模拟实现银行业务调度系统逻辑,具体需求如下:
* 银行内有6个业务窗口,1-4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口
* 有3种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类的业务)。
* 伊布随机生成各种类型的客户,生成各类型用户比例为:
VIP客户:普通客户:快速客户 = 1 : 6 : 3
* 客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需时间,
快速客户办理业务所需时间为最小值(提示;办理业务的过程可通过线程的sleep方式模拟)
* 各类型客户在其对应的窗口按顺序依次办理业务
* 当VIP窗口(6号窗口)和快速窗口(5号窗口)没有客户等待办理业务时,两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理,则优先处理对应客户的业务
* 随机生成客户时间间隔,业务办理时间最大值和最小值自定,可以设置
* 不要求实现GUI,只考虑系统逻辑实现,可通过log方式展现程序运行结果
分析:
日常去银行办理业务可以发现客户到银行办理事务时,先要去取号机领取一个服务号码,然后等待服务窗口叫号,而且不同的窗口会处理不同的事务。
1.分析取号机
取号机在银行中一般只有1个,但是可以取出不同类型的服务号码(VIP、快速、普通)。
* 取号机需要用单例模式定义,因为只有1个
* 取号机可以取3种不同类型的服务号码,取号机提供get()方法,进行取号。
2.分析取号过程
取号机先要产生一个号码,取出号码要加入等待队列中,然后等待窗口叫号。
* 需要一个可变数组来存放被取出的号码(即等待队列),号码机产生的号码每取一次要加1。
* 取号机产生号码应该有个初始值,这里从1开始,然后客户通过一个方法获得号码并添加到等待队列中。
* 窗口叫号,会从等待序列中的获得一个最早号码,并把这个号码从队列中移除
3.分析服务窗口
每个窗口都会处理对应自己类型的服务,而且同一类型的窗口可能会有多个,因此需要窗口编号。每个窗口处理事务的时间也不一样,因此需要多线程来操作所有窗口。窗口服务时有时间规定的,应该有个最大时间和最小时间,工作的时间就应该在这个时间范围内。
* 通过set()方法来设定窗口号和窗口类型,默认窗口号从1开始,类型是普通窗口
* 使用线程池,根据类型判断需要开启哪种类型的窗口,来开启多个窗口服务线程
* 每次窗口处理事务需要根据规定的最大最小时间计算服务时间
* 3种类型的窗口除了普通窗口外,其他的窗口在没有对应客户等待时,处理普通类型服务,这里可以复用普通窗口的代码
4.固定的类型
固定3种类型,因此使用枚举
* 3种类型分别是普通、快速、VIP。向外提供toString()方法,显示对应的汉字
5.用到的常量
对于常量,应该使用一个单独的类来保存
* 这里用到了最大服务时间、最小服务时间、普通客户取号间隔时间、快速客户取号间隔时间和VIP客户取号间隔时间
6.运行测试
先开启各个窗口,按着需求开启4个普通窗口,1个快速窗口,1个VIP窗口。然后使用定时器控制客户取号,根据客户比例控制取号间隔时间。
代码:
1.取号机
3.服务窗口
4.类型枚举类
5.常量类
需求:
模拟实现银行业务调度系统逻辑,具体需求如下:
* 银行内有6个业务窗口,1-4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口
* 有3种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类的业务)。
* 伊布随机生成各种类型的客户,生成各类型用户比例为:
VIP客户:普通客户:快速客户 = 1 : 6 : 3
* 客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需时间,
快速客户办理业务所需时间为最小值(提示;办理业务的过程可通过线程的sleep方式模拟)
* 各类型客户在其对应的窗口按顺序依次办理业务
* 当VIP窗口(6号窗口)和快速窗口(5号窗口)没有客户等待办理业务时,两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理,则优先处理对应客户的业务
* 随机生成客户时间间隔,业务办理时间最大值和最小值自定,可以设置
* 不要求实现GUI,只考虑系统逻辑实现,可通过log方式展现程序运行结果
分析:
日常去银行办理业务可以发现客户到银行办理事务时,先要去取号机领取一个服务号码,然后等待服务窗口叫号,而且不同的窗口会处理不同的事务。
1.分析取号机
取号机在银行中一般只有1个,但是可以取出不同类型的服务号码(VIP、快速、普通)。
* 取号机需要用单例模式定义,因为只有1个
* 取号机可以取3种不同类型的服务号码,取号机提供get()方法,进行取号。
2.分析取号过程
取号机先要产生一个号码,取出号码要加入等待队列中,然后等待窗口叫号。
* 需要一个可变数组来存放被取出的号码(即等待队列),号码机产生的号码每取一次要加1。
* 取号机产生号码应该有个初始值,这里从1开始,然后客户通过一个方法获得号码并添加到等待队列中。
* 窗口叫号,会从等待序列中的获得一个最早号码,并把这个号码从队列中移除
3.分析服务窗口
每个窗口都会处理对应自己类型的服务,而且同一类型的窗口可能会有多个,因此需要窗口编号。每个窗口处理事务的时间也不一样,因此需要多线程来操作所有窗口。窗口服务时有时间规定的,应该有个最大时间和最小时间,工作的时间就应该在这个时间范围内。
* 通过set()方法来设定窗口号和窗口类型,默认窗口号从1开始,类型是普通窗口
* 使用线程池,根据类型判断需要开启哪种类型的窗口,来开启多个窗口服务线程
* 每次窗口处理事务需要根据规定的最大最小时间计算服务时间
* 3种类型的窗口除了普通窗口外,其他的窗口在没有对应客户等待时,处理普通类型服务,这里可以复用普通窗口的代码
4.固定的类型
固定3种类型,因此使用枚举
* 3种类型分别是普通、快速、VIP。向外提供toString()方法,显示对应的汉字
5.用到的常量
对于常量,应该使用一个单独的类来保存
* 这里用到了最大服务时间、最小服务时间、普通客户取号间隔时间、快速客户取号间隔时间和VIP客户取号间隔时间
6.运行测试
先开启各个窗口,按着需求开启4个普通窗口,1个快速窗口,1个VIP窗口。然后使用定时器控制客户取号,根据客户比例控制取号间隔时间。
代码:
1.取号机
package com.interview.bank;
/*
* 取号机,3种号码类型
* 1个取号机,提供3种不同类型的号码
*/
public class NumberMachine {
//普通号码
private NumberManager commonManager = new NumberManager();
//VIP号码
private NumberManager VIPManager = new NumberManager();
//快速号码
private NumberManager expressManager = new NumberManager();
//为3种号码提供获取方法
public NumberManager getCommonManager() {
return commonManager;
}
public NumberManager getVIPManager() {
return VIPManager;
}
public NumberManager getExpressManager() {
return expressManager;
}
//号码种类不同,但是取号机就一个,单例模式
private NumberMachine(){}
private static NumberMachine numberMachine = null;
public static NumberMachine newInstance(){
if(numberMachine==null){
synchronized(NumberMachine.class){
if(numberMachine==null){
numberMachine = new NumberMachine();
}
}
}
return numberMachine;
}
}
2.取号过程
package com.interview.bank;
/*
* 取号过程管理
* 客户取号,从1开始取服务等待号码
*/
import java.util.ArrayList;
import java.util.List;
public class NumberManager {
//取号机依次发放的号码,号码从1开始
private int lastNumber = 1;
//用来存放被取的号码队列(即等待服务的号码队列),这里使用集合便于动态加载
private List<Integer> serviceNumbers = new ArrayList<Integer>();
/*
* 小细节:
* 1. 客户取号和窗口处理都要同步,不然就会出现多个人取到同一个号的现象
* 这里使用了synchronized关键字保持同步
* 2. 要注意的是号码队列一开始是空的 ,如果在没有存数据的时候就被人调用remove()方法,就会报空指针异常
* 这里将返回值int 改成Integer,
* 初始化的lastNumber虽然是int,不过java可以自动装箱和拆箱
*/
//客户取服务号,并将号码添加到待服务号码队列中
public synchronized Integer generateNewManager(){
serviceNumbers.add(lastNumber);
return lastNumber++;
}
//处理服务号,按顺序为处于等待的号码进行服务,服务后该号码作废
public synchronized Integer fetchServiceNumber(){
Integer num = null;
if(serviceNumbers!=null&&serviceNumbers.size()>0){
return serviceNumbers.remove(0);
}
return num;
}
}
3.服务窗口
package com.interview.bank;
/*
* 服务窗口:
* 模拟银行职员在窗口叫号,然后处理事务的情景
*/
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServiceWindow {
//窗口号,从1号开始
private int windowID = 1;
//服务窗口类型,默认是普通窗口
private CustomerType type = CustomerType.COMMON;
/*
* set()方法,用于设置窗口号和窗口类型
*/
//设置窗口号
public void setWindowID(int windowID){
this.windowID = windowID;
}
//设置窗口类型
public void setType(CustomerType type){
this.type = type;
}
//进行窗口取号,不同类型的窗口,服务的客户类型也不同,因为每个窗口处理的时间不同,这里使用多线程执行窗口服务
public void start(){
ExecutorService esPool = Executors.newSingleThreadExecutor();
esPool.execute(new Runnable(){
@Override
public void run() {
while(true){
//这里不是if else ,是因为使用switch效率要高
//根据不同的类型进行服务
switch(type){
case COMMON:
commonWindow();
break;
case EXPRESS:
expressWindow();
break;
case VIP:
vipWindow();
break;
}
}
}
});
}
//普通服务窗口
private void commonWindow() {
//获得窗口名称
String windowName = "第"+windowID+"号"+type+"窗口";
//获得普通客户等待号码
Integer number = NumberMachine.newInstance().getCommonManager().fetchServiceNumber();
System.out.println(windowName+"开始取号");
if(number!=null){
System.out.println(windowName+"正在为第"+number+"个"+"普通客户服务");
//服务时间,开始计时
long begin = System.currentTimeMillis();
//服务时间应该在规定的最大最小服务时间范围中
int maxRandomValue = Constants.MAX_SERVICETIME-Constants.MIN_SERVICETIME;
long serviceTime = new Random().nextInt(maxRandomValue)+1+Constants.MIN_SERVICETIME;
try {
//系统等待,模拟服务所消耗的时间
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(windowName+"为第"+number+"个"+"普通客户完成服务,耗时"+(end-begin)/1000+"秒");
}else{
//如果没有客户等待服务,等待1秒
System.out.println(windowName+"没有取到号码,休息一下,1秒后再次取号");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//快速服务窗口
private void expressWindow() {
//获得窗口名称
String windowName = "第"+windowID+"号"+type+"窗口";
//获得快速客户等待号码
Integer number = NumberMachine.newInstance().getExpressManager().fetchServiceNumber();
System.out.println(windowName+"开始取号");
if(number!=null){
System.out.println(windowName+"正在为第"+number+"个"+type+"客户服务");
//服务时间,开始计时
long begin = System.currentTimeMillis();
//快速窗口在处理快速服务时,总是用最短的时间
try {
Thread.sleep(Constants.MIN_SERVICETIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(windowName+"为第"+number+"个"+type+"客户完成服务,耗时"+(end-begin)/1000+"秒");
}else{
//如果没有快速客户在等待,则获取普通客户等待号码,处理普通服务
System.out.println(windowName+"没有取到号码");
commonWindow();
}
}
//VIP服务窗口
private void vipWindow() {
//获得窗口名称
String windowName = "第"+windowID+"号"+type+"窗口";
//获得VIP客户等待号码
Integer number = NumberMachine.newInstance().getVIPManager().fetchServiceNumber();
System.out.println(windowName+"开始取号");
if(number!=null){
System.out.println(windowName+"正在为第"+number+"个"+type+"客户服务");
//服务时间,开始计时
long begin = System.currentTimeMillis();
//VIP窗口这是优先处理VIP客户,而服务时间应该在规定的最大最小服务时间范围中
int maxRandomValue = Constants.MAX_SERVICETIME-Constants.MIN_SERVICETIME;
long serviceTime = new Random().nextInt(maxRandomValue)+1+Constants.MIN_SERVICETIME;
try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(windowName+"为第"+number+"个"+type+"客户完成服务,耗时"+(end-begin)/1000+"秒");
}else{
//如果没有VIP客户在等待,则获取普通客户等待号码,处理普通服务
System.out.println(windowName+"没有取到号码");
commonWindow();
}
}
}
4.类型枚举类
package com.interview.bank;
/*
* 类型枚举:
* 一共3种类型,分别为普通(COMMON),快速(EXPRESS),VIP
*/
public enum CustomerType {
//分别为普通类型,快速类型,VIP类型
COMMON,VIP,EXPRESS;
//设定类型输出文字
public String toString(){
switch(this){
case COMMON:
return "普通";
case EXPRESS:
return "快速";
case VIP:
return "VIP";
}
return null;
}
}
5.常量类
package com.interview.bank;
/*
* 常量:
* 保存用到的所有常量,包括最大和最小服务时间,各种类型客户的取号间隔时间(根据间隔时间来划分各类型客户的比例)
*/
public class Constants {
//最大服务时间
public static int MAX_SERVICETIME = 10000;
//最小服务时间
public static int MIN_SERVICETIME = 1000;
//普通客户取号间隔时间
public static int COMMON_COSTOMER_TIME = 1;
//快速客户取号间隔时间
public static int EXPRESS_COSTOMER_TIME = 3;
//VIP客户取号间隔时间
public static int VIP_COSTOMER_TIME = 6;
}
6.运行测试
package com.interview.bank;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/*
* 运行测试:
* 创建服务窗口:4个普通的,1个快速的,1个VIP的
* 使用调度线程模拟各种客户取号
*/
public class MainClass {
public static void main(String[] args) {
//创建4个普通窗口
for(int i=1;i<4;i++){
//窗口默认类型为普通
ServiceWindow commonWindow = new ServiceWindow();
//设置窗口号
commonWindow.setWindowID(i);
//开启窗口服务线程
commonWindow.start();
}
//创建1个VIP窗口
ServiceWindow vipWindow = new ServiceWindow();
//设置窗口类型为VIP
vipWindow.setType(CustomerType.VIP);
//开启窗口服务线程
vipWindow.start();
//创建1个快速窗口
ServiceWindow expressWindow = new ServiceWindow();
//设置窗口类型为快速
expressWindow.setType(CustomerType.EXPRESS);
//开启窗口服务线程
expressWindow.start();
// 使用定时器为不同类型的客户取号,通过设置时间间隔控制客户类型比例
//普通客户取号,每隔1秒取1次
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
@Override
public void run() {
//普通客户取号等待
Integer number = NumberMachine.newInstance().getCommonManager().generateNewManager();
System.out.println(number+"号普通客户等待服务");
}
},
0,
Constants.COMMON_COSTOMER_TIME,
TimeUnit.SECONDS);
//快速客户取号,每隔3秒取1次
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
@Override
public void run() {
//普通客户取号等待
Integer number = NumberMachine.newInstance().getExpressManager().generateNewManager();
System.out.println(number+"号快速客户等待服务");
}
},
0,
Constants.EXPRESS_COSTOMER_TIME,
TimeUnit.SECONDS);
//VIP客户取号,每隔6秒取1次
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
@Override
public void run() {
//普通客户取号等待
Integer number = NumberMachine.newInstance().getVIPManager().generateNewManager();
System.out.println(number+"号VIP客户等待服务");
}
},
0,
Constants.VIP_COSTOMER_TIME,
TimeUnit.SECONDS);
}
}