银行服务仿真
摘要
本文对银行服务系统的仿真进行了基于自己理解的程序编写, 文中详细记录了思考过程和程序完善的几个要点, 没有参考张孝祥老师的视频, 文中用到方法完全基于传智博客毕老师基础视频讲座, 作为这一个半月来自学视频教程的项目实战.
------------------------------------------------------------------------------
6/10/2013 Updates
最近思考了一下, 文中将CustomerQueueIn/Out动作封装成类的方式实在是太弱了, 这两个类其实只应该是操作队列的两个方法, 而且这两个方法应该在描述队列的类内, 而且, 分析需求只有3个队列, 用枚举是一个很好的选择. 此处的方法过度设计了, 详见Java——银行服务(Revised), 6/20/2013前整理完毕.
I. 需求
1. 银行服务: 3种类型 普通客户, VIP客户, 快速客户; 访问比例: 6:1:3
2. 银行6个服务窗口: 4个普通客户窗口, 1个VIP窗口, 1个快速客户窗口
3. 当VIP,快速窗口没人排队时,转成普通客户窗口, 但一旦有VIP或快速客户, 在处理完当前客户后,优先服务窗口对应的VIP或快速客户;
4. 普通客户和VIP客户的服务时间是一个最大最小值间的随机数, 快速客户的服务时间是最小值
5. 客户的访问频率自定义, 服务时间最大,最小值自定, 但是, 后期可变
II. 思考过程
1. 三种类型先等效成一种类型来看, 假设只有普通用户,那么单纯实现普通用户的银行服务需要哪些步骤:
1.1 队列——集合的思想: ArrayList<CommonCustomer> 定义CommonCustomer类, 集合队列存储
------------------------------------------------------------------------------------------------------------------------------------------
1.2 窗口服务, 相当于集合remove(0)元素的过程, 而多个窗口服务, 相当于多线程操作共有资源, Thread(Runnable)方式
1.2.1 定义 CommonCustomerQueueOut implements Runnable, 传递给窗口Thread
1.2.2 为可读性, 定义窗口CommonCustomerWindow extends Thread; 专门负责ArrayList<CommonCustomer>的取操作
------------------------------------------------------------------------------------------------------------------------------------------
1.3 顾客进入银行排队, 相当于一个集合add(CommonCustomer)元素的过程, 也用线程操作Thread(Runnable), 以对应1.2.1
1.3.1 定义 CommonCustomerQueueIn implements Runnable, 传给随机生成CommonCustomer的Thread
1.3.2 随机生成CommonCustomer的Thread不再定义直接Thread(CommonCustomerQueueIn);
===========================================================================================
2. 再考虑3种客户, 就是一样的过程了, 只是, 有一点特殊, 如何实现当VIP和快速SWIFT队列无人等待时, 自动为CommonCustomer服务?
2.1 本质问题是如何能让一个线程同时具有两种动作属性, 以VIPCustomerWindow为例, 如何让它即可以拥有VIPCustomerQueueOut和CommonCustomerQueueOut的动作
2.2 采用Thread(Runnable)的形式时, run方法在Runnable对象VIP/CommonCustomerQueueOut中, 这是两个,现在要将这两个run合并成一个run, 用Thread(Runnable)已经不合适
2.3 但是, 如果直接继承, 比如 VIPCustomoerWindow extends Thread, 并将两个队列取出动作传给这个VIPCustomerWindow(VIPCustomerQueueOut,CommonCustomerQueueOut), 并在run函数中同时对VIP/CommonCustomerQueueOut对应的ArrayList<VIP/CommonCustomer>队列进行操作, 就达到了合二为一的效果
2.4 run函数中判断ArrayList<VIPCustomer>集合是否为空, 空的时候对ArrayList<CommonCustomer>进行操作
===========================================================================================
3. 剩下的就是一些细节问题了, 以CommonCustomer为例说明
3.1 如何随机生成服务时间?
3.1.1 每个CommonCustomer对象里面有一个getServiceTime()函数,可以返回一个服务时间
3.1.2 每个CommonCustomer对象有一个min max成员, 是最小和最大等待时间, 单位ms, 可以通过getMin; getMax得到这个值
3.1.3 每个CommonCustomer可以调用其CommonCustomer(min,max)来初始化,
3.1.4 操作CommonCustomerQueueIn的方法同样提供CommonCustomerQueueIn(xxx,xxx ,min,max)的方法,将min,max对外暴露出去以方便赋值
3.1.5 如果min>max 抛出RuntimeException
------------------------------------------------------------------------------------------------------------------------------------------
3.2 模拟顾客随机进入银行相应队列
3.2.1 有一个最小时间: minTime, 将这个minTime 传给CommonCustomerQueueIn(xxx, minTime,min,max)构造函数
3.2.2 CommonCustomerQueueIn中提供一个randomTime()函数,
3.2.3 线程利用randomTime()生成的这个时间可以进入wait状态
3.2.4 在MainClass中, 定义三中minTime CC_IN_TIME : VIP_IN_TIME : SWIFT_IN_TIME= 1/6:1/1:1/3;
------------------------------------------------------------------------------------------------------------------------------------------
3.3 为了打印结果方便, 每一个客户都会达到一个ticket, 通过getTicket; setTicket函数可以初始化和得到 顾客类型和序号: eg. ComCustomer-8;
------------------------------------------------------------------------------------------------------------------------------------------
3.4 为了保证数据操作的安全, 对每个操作同一个资源例如ArrayList<CommonCustomer> al 的线程们进行synchronize(al) 的操作, 为了不影响效率, 窗口服务,等顾客都是al.wait()操作
------------------------------------------------------------------------------------------------------------------------------------------
3.5 抽取3中类型的顾客为一个父类Customer Common/VIP/SWIFTCustomer都继承这个父类
------------------------------------------------------------------------------------------------------------------------------------------
3.6 演示结果的时候发现忘记了考虑一种情况, 即如果CommonCustomerWindow1-4没有满的情况下,是不应该让VIP/SWIFTCustomerWindow接待CommonCustomer的
3.6.1为此,需要VIP/SWIFTCustomerWindow可以监视CommonCustomerWindow窗口的运行状态
3.6.2 需要将CommonCustomerWindow1-4作为一个CommonCustomerWindow[] 数组传递给VIP/SWIFTCommonCustomer
3.6.3 监测CommonCustomerWindow的标志位 occupied, 这个标志位是从Runnable的CommonCustomerQueueOut的动作的occupied标志得来的
III 代码
III.i. 定义三类顾客的代码
/*
* 父类: Customer
* 子类: CommonCustomer,VIPCustomer,SWIFTCustomer
* - 可以随机产生一个服务时间
* - 可以获得排号信息和服务种类信息
*/
public class Customer { // --> 3.5
// 构造函数 |--
public Customer(){}
public Customer(long min,long max){ // --> 3.1.3
if (min>max)
throw new RuntimeException("min cannot be larger than max");
this.max=max;
this.min=min;
}
// --|
// 可以随机产生一个服务时间 |--
private long min=2000; // --> 3.1.2
private long max=4000; // --> 3.1.2
public long getMin() {return min;} // --> 3.1.2
public long getMax() {return max;} // --> 3.1.2
public void setMinMax(long min,long max) {
this.min = min;
this.max=max;
if (min>max) // --> 3.1.5
throw new RuntimeException("min cannot be larger than max");
}
public long getServiceTime(){ // --> 3.1.1
return min+(long)(Math.random())*(max-min);
}
// --|
// 可以获得排号信息和服务种类信息 |--
private String ticket;
public String getTicket(){return ticket;}
public void setTicket(String ticket){this.ticket=ticket;}
// --|
}
class CommonCustomer extends Customer {
public CommonCustomer(){}
public CommonCustomer(long min,long max){
super(min,max);
}
}
class VIPCustomer extends Customer{
public VIPCustomer(){}
public VIPCustomer(long min,long max){
super(min,max);
}
}
// SWIFTCustomer 服务时间为最小值
class SWIFTCustomer extends Customer{
public SWIFTCustomer(){}
public SWIFTCustomer(long min,long max){
super(min,max);
}
public long getServiceTime(){
return super.getMin();
}
}
III.ii CommonCustomer的操作代码
/*
* - 提供对一个ArrayList<CommonCustomer>的存入操作
* - implements Runnbale 复写run方法
* - 抽取同步段方法comeIn在run中反复调用
* - 提供一个供线程等待的随机时间
*/
import java.util.ArrayList;
public class CommonCustomerQueueIn implements Runnable { // --> 1.3.1
private ArrayList<CommonCustomer> al;
private long minTime;
private long min=2000;
private long max=4000;
public CommonCustomerQueueIn(ArrayList<CommonCustomer> al,long minTime){
this.al=al;
this.minTime=minTime; // --> 3.2.1
}
public CommonCustomerQueueIn(ArrayList<CommonCustomer> al,long minTime,long min,long max){
this.al=al; // --> 3.1.4
this.minTime=minTime;
this.min=min;
this.max=max;
if (min>max) // --> 3.1.5
throw new RuntimeException("min cannot be larger than max");
}
private int num=0;
public void run(){
while(true){
try{Thread.sleep(10);}catch(Exception e){};
comeIn();
}
}
private void comeIn(){ // --> 3.4
synchronized(al){
try{
al.wait(randomTime()); // --> 3.2.3
}catch(Exception e){}
CommonCustomer cc=new CommonCustomer(min,max);
cc.setTicket("Common Customer-"+(++num));
al.add(cc);
System.out.println(cc.getTicket()+" comes in");
}
}
private long randomTime(){ // --> 3.2.2
return (long)(minTime*5*Math.random())+minTime;
}
}
/*
* - 定义一个移除ArrayList<CommonCustomer>的操作
* - 抽取移除操作为comeOut(),在run()函数中反复调用
*/
import java.util.ArrayList;
public class CommonCustomerQueueOut implements Runnable{ // --> 1.2.1
// 构造函数 |--
public CommonCustomerQueueOut(ArrayList<CommonCustomer> al,String window){
this.al=al;
this.window=window;
}
// --|
private ArrayList<CommonCustomer> al;
private String window;
public String getWindow(){return window;}
// 线程部分 |--
public void run(){
while(true){
try{Thread.sleep(10);}catch(Exception e){};
comeOut();
}
}
public void comeOut(){
synchronized(al){
if(al.size()!=0){
try{
CommonCustomer cc=al.remove(0);
System.out.println(cc.getTicket()+"'s turn to "+getWindow());
al.wait(cc.getServiceTime());
System.out.println(cc.getTicket()+" finished.");
}catch(Exception e){}
}
}
}
// --|
}
/*
* 为增强可读性而添加的类CommonCustomerWindow,负责接受CommonCustomerQueueOut
* 如果没有这个类,VIP/SWIFT的接到CommonCustomer的动作就无法完成
*/
public class CommonCustomerWindow extends Thread{ // -->1.2.2
private CommonCustomerQueueOut target;
public CommonCustomerWindow(Runnable target,String name){
super(target,name);
}
public boolean isOccupied() { // --> 3.6.3
return target.isOccupied(); // --> 3.6.3
}
}
III.iii VIPCustomer操作的代码
注1: VIP/SWIFTCustomer代码完全一致,将VIP代码中所有的VIP替换成SWIFT就是SWIFT的代码
注2: VIPCustomerQueueIn/Out的代码和CommonCustomerQueueIn/Out的代码一致, 将代码中所有的Common换成VIP即得到VIPCustomerQueueIn代码
所以, 此处只给出特别的代码部分VIPCustomerWindow
/*
* VIPCustomerWindow通过对
* VIPCustomerQueueOut和空闲时对CommonCustomerQueueOut
* 对ArrayList<VIPCustomer>和ArrayList<CommonCustomer>
* 移除元素
*/
public class VIPCustomerWindow extends Thread{ // --> 2.3
private VIPCustomerQueueOut vip;
private CommonCustomerQueueOut cc;
private CommonCustomerWindow[] ccWindows; // --> 3.6.2
// --> 2.3
public VIPCustomerWindow(VIPCustomerQueueOut vip,CommonCustomerQueueOut cc,
CommonCustomerWindow[] ccWindows,String window){
super(window);
this.vip=vip;
this.cc=cc;
this.ccWindows=ccWindows;
}
public void run(){
while(true){
try{Thread.sleep(10);}catch(Exception e){};
while(vip.getQueue().size()!=0){ // --> 2.4
vip.comeOut();
}
//System.out.println("VIP Queue is Empty========");
synchronized(cc.getArrList()){ // 3.6 -->
boolean allOccupied=true;
for(CommonCustomerWindow t:ccWindows){
allOccupied=t.isOccupied()&&allOccupied;
}
if(allOccupied)
cc.comeOut(); // --> 3.6
}
}
}
}
IV 演示
/*
* 此段代码完整了4个普通窗口,1个VIP窗口和1个快速窗口的银行服务仿真
* CommonCustomer用到:
* CommonCustomerQueueIn;CommonCustomerQueueOut;CommonCustomerWindow
* VIPCustomer用到:
* VIPCustomerQueueIn;VIPCustomerQueueOut;VIPCustomerWindow
* SWIFTCustomer用到:
* SWIFTCustomerQueueIn;SWIFTCustomerQueueOut;SWIFTCustomerWindow
*/
import java.util.ArrayList;
public class MainClass {
public static void main(String[] args) {
// 顾客访问随机数的基准值及最大最小服务时间
final long CC_IN_TIME=300;
final long VIP_IN_TIME=1800;
final long SWIFT_IN_TIME=600;
final long MIN_SERVICE_TIME=2000;
final long MAX_SERVICE_TIME=4000;
// 三种队列定义
ArrayList<CommonCustomer> ccAl=new ArrayList<CommonCustomer>();
ArrayList<VIPCustomer> vipAl=new ArrayList<VIPCustomer>();
ArrayList<SWIFTCustomer> swiftAl=new ArrayList<SWIFTCustomer>();
// 4个普通客户出队列线程
CommonCustomerWindow ccw1=new CommonCustomerWindow(
new CommonCustomerQueueOut(ccAl,"ComWindow-1"),"ComWindow-1");
CommonCustomerWindow ccw2=new CommonCustomerWindow(
new CommonCustomerQueueOut(ccAl,"ComWindow-2"),"ComWindow-2");
CommonCustomerWindow ccw3=new CommonCustomerWindow(
new CommonCustomerQueueOut(ccAl,"ComWindow-3"),"ComWindow-3");
CommonCustomerWindow ccw4=new CommonCustomerWindow(
new CommonCustomerQueueOut(ccAl,"ComWindow-4"),"ComWindow-4");
CommonCustomerWindow[] ccWindows=new CommonCustomerWindow[]{ccw1,ccw2,ccw3,ccw4};
// VIP和SWIFT客户出队列线程
Thread vipw=new VIPCustomerWindow(new VIPCustomerQueueOut(vipAl,"VIPWindow"),
new CommonCustomerQueueOut(ccAl,"VIPWindow"),ccWindows,"VIPWindow");
Thread swiftw=new SWIFTCustomerWindow(new SWIFTCustomerQueueOut(swiftAl,"SWIFTWindow"),
new CommonCustomerQueueOut(ccAl,"SWIFTWindow"),ccWindows,"SWIFTWindow");
// 三种客户进队列进程
Thread ccqi=new Thread(new CommonCustomerQueueIn(ccAl,CC_IN_TIME,MIN_SERVICE_TIME,MAX_SERVICE_TIME));
Thread vipqi=new Thread(new VIPCustomerQueueIn(vipAl, VIP_IN_TIME,MIN_SERVICE_TIME,MAX_SERVICE_TIME));
Thread swiftqi=new Thread(new SWIFTCustomerQueueIn(swiftAl,SWIFT_IN_TIME,MIN_SERVICE_TIME,MAX_SERVICE_TIME));
// 开启所有线程
ccw1.start(); ccw2.start();ccw3.start();ccw4.start();
vipw.start(); swiftw.start();
ccqi.start(); vipqi.start(); swiftqi.start();
}
}
IV.i 测试CommonCustomer很多, SWIFT/VIPCustomer很少时
预测结果, SWIFT/VIP窗口接待CommonCustomer但是出现SWIFT/VIP等待时结束当前CommonCustomer服务SWIFT/VIP
CC_IN_TIME:VIP_IN_TIME:SWIFT_IN_TIME=50:500:500;
输出:
Common Customer-1 comes in
Common Customer-1's turn to ComWindow-3
Common Customer-2 comes in
Common Customer-2's turn to ComWindow-2
Common Customer-3 comes in
Common Customer-3's turn to ComWindow-1
Common Customer-4 comes in
Common Customer-4's turn to ComWindow-4
Common Customer-5 comes in
Common Customer-5's turn to SWIFTWindow // ---> CC5 to SWIFT
Common Customer-6 comes in
Common Customer-6's turn to VIPWindow // ---> CC6 to VIP
Common Customer-7 comes in
Common Customer-1 finished.
Common Customer-8 comes in
Common Customer-7's turn to ComWindow-3
Common Customer-2 finished.
Common Customer-8's turn to ComWindow-2
Common Customer-9 comes in
Common Customer-3 finished.
Common Customer-9's turn to ComWindow-1
Common Customer-10 comes in
SWIFT Customer-1 comes in // ---> SWIFT1 wait for CC5
VIP Customer-1 comes in // ---> VIP1 wait for CC6
Common Customer-11 comes in
Common Customer-4 finished.
Common Customer-10's turn to ComWindow-4
Common Customer-12 comes in
SWIFT Customer-2 comes in
Common Customer-5 finished. // --> CC5 done
SWIFT Customer-1's turn to SWIFTWindow // --> SWIFT1 to SWIFT
Common Customer-13 comes in
Common Customer-6 finished. // --> CC6 done
VIP Customer-1's turn to VIPWindow // --> VIP1 to VIP
Common Customer-14 comes in
输出符合预期
IV.ii 测试题目中的6:1:3的需求
CC_IN_TIME:VIP_IN_TIME:SWIFT_IN_TIME=300:1800:600;
输出:
SWIFT Customer-1 comes in
SWIFT Customer-1's turn to SWIFTWindow
Common Customer-1 comes in
Common Customer-1's turn to ComWindow-2
Common Customer-2 comes in
Common Customer-2's turn to ComWindow-4
SWIFT Customer-2 comes in
SWIFT Customer-1 finished.
SWIFT Customer-2's turn to SWIFTWindow
Common Customer-3 comes in
Common Customer-3's turn to ComWindow-1
Common Customer-1 finished.
Common Customer-2 finished.
SWIFT Customer-3 comes in
Common Customer-4 comes in
Common Customer-4's turn to ComWindow-2
SWIFT Customer-2 finished.
SWIFT Customer-3's turn to SWIFTWindow
Common Customer-3 finished.
Common Customer-5 comes in
Common Customer-5's turn to ComWindow-4
Common Customer-4 finished.
SWIFT Customer-3 finished.
Common Customer-6 comes in
Common Customer-6's turn to ComWindow-2
SWIFT Customer-4 comes in
SWIFT Customer-4's turn to SWIFTWindow
Common Customer-5 finished.
VIP Customer-1 comes in
VIP Customer-1's turn to VIPWindow
Common Customer-7 comes in
Common Customer-7's turn to ComWindow-3
Common Customer-6 finished.
SWIFT Customer-4 finished.
Common Customer-8 comes in
Common Customer-8's turn to ComWindow-4
VIP Customer-1 finished.
SWIFT Customer-5 comes in
SWIFT Customer-5's turn to SWIFTWindow
Common Customer-7 finished.
Common Customer-9 comes in
Common Customer-9's turn to ComWindow-3
Common Customer-8 finished.
Common Customer-10 comes in
Common Customer-10's turn to ComWindow-4
SWIFT Customer-5 finished.
Common Customer-9 finished.
Common Customer-11 comes in
Common Customer-11's turn to ComWindow-2
V. 代码简化方向分析
1. 此银行系统代码中的三类有很强的相似性, 应该可以通过一种方式将三类代码简化为一类代码
2. 一种可能的想法是添加泛型, 比如CustomerQueueIn<T extends Customer>
3. 但是用这种泛型有一个问题无法解决, 即如何在T不确定的情况下在CustomerQueueIn内部生产一个对象并添加如集合ArrayList<T> al; al.add(new T());
4. 又想到, 可能能通过反射做到, 但目前的对反射的理解是也是从字节码上获得信息, 可是, 这个T的具体类型是在main方法中才会出现的,即运行期才能得到
5. 莫非要通过对main所在的类进行反射来得到T的具体的.class然后newInstance?
6. 这个问题还有待进一步讨论
VI 总结
1. 本文对银行服务的仿真问题进行了基于自己理解的做法
2. 对比之前做交通系统的经验, 运用了通过ArrayList来模拟一个队列
3. 代码还可以仅一步优化, 可以利用泛型和反射的机制来简化代码
4. 最终目标是简化到CustomerQueueIn<T extends Customer>; CustomerQueueOut<T extends Customer>; CommonCustomerWindow;SpecialCustomerWindow<T extends Customer>; 四个操作类