您的位置:首页 > 职场人生

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

2013-07-09 20:29 399 查看
-------android培训、java培训、期待与您交流!
----------

模拟实现银行业务调度系统逻辑,具体需求如下:
Ø银行内有6个业务窗口,1-4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。
Ø有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。
Ø异步随机生成各种类型的客户,生成各类型用户的概率比例为:
VIP客户:普通客户:快速客户=1:6:3。
Ø客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。
Ø各类型客户在其对应窗口按顺序依次办理业务。



Ø当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。

Ø随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。
Ø不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。

该系统中,不变的是号码产生器。号码产生器中有三个号码控制器产生三种号码。只有一个机器,故为单例模式。对于窗口,有开始服务程序。Jdk1.6版本中,应用线程池。


先分析程序中的类
(1)也是先得到枚举类,它一般都是在很多类中都用得上,客户的类型肯定数一个枚举类
(2)号码的产生和使用,肯定是一个生产者消费者模式的双线程。号码的产生设计在取号机类中,这个类一被创建就过一段时间取一个号,这个号码的类型应该是按照VIP客户:普通客户:快速客户=1:6:3的比例来设计。我们设计一个随机数生成器,取1-10,根据产生的随机数来确定类型,每种类型所对应的数字个数的比例刚好是1:6:3。
(3)相应与(2)号码的“消费”也应该在一个类中,这个类是窗口类,窗口应该有个枚举的成员变量在构造方法时传入确定窗口类型。窗口最好设计为一个超类,让三种窗口都季承于它,但是这里VIP和快速窗口也处理普通客户业务,它们要用到其它类的方法,为了方便把它们写成一个类。类里面应该有三个方法,每个方法是每种类型窗口的服务实现。
(4)号码应该存在一个容器中,这个容器所属的类应该单独定义,以便好控制并发访问时候锁的处理,把这个类设计成号码管理类,不管是取号机还是窗口,操作号码都要通过它。所以在取号机和窗口类中都要有,号码管理器的一个实例,为了确保锁的安全,这个实例最好还应该设计成单例。
(5)最后是主类,在主类中让生产消费线程都启动起来,创建调用摇号机实例调用方法,创建窗口实例调用方法即可。
(6)为了让其它类不能创建取号机和窗口实例,调用里面的方法,所以把主类设计成单例,其它类构造要传入主类对象,这样只能在主类中构造其它类,并调用其它类的方法。
[/code]

代码如下:

packagecom.interview.bank;

/**
*服务类型的枚举类,有三种普通、快速和VIP。其中每个元素都重写了其toString方法。
**/
publicenumCustomerType{
/**
*普通类型
**/
COMMEN{
publicStringtoString(){
return"普通";
}
},
/**
*快速类型
**/
EXPRESS{
publicStringtoString(){
return"快速";
}
},
/**
*VIP类型
**/
VIP{
publicStringtoString(){
returnname();
}
};
}

取号机类

packagecom.interview.bank;

importjava.util.Random;
importjava.util.concurrent.Executors;
importjava.util.concurrent.TimeUnit;

/**
*取号机器类,该类主要负责号码对象的产生和确定号码对象类型(客户类型)。
**/
publicclassNumberMachine{
privatestaticRandomr=newRandom();
privatestaticintcustomerNum=0;
privateNumberManagermm=null;

privatestaticintgetCustomerNum(){
return(++customerNum);
}

/**
*取号机的构造方法。由于要操作渠道的号,所以需要一个专门操作客户号码的号码管理器类对象。
*并且在取号机一出来就开始工作,即创建一个调度线程模拟客户的产生和客户选择的服务类型。
*这里有一个随机数生成器来模拟客户选择的服务类型。0-2表示是快速类型客户,3表示是VIP类型客户,4-9表示是普通类型客户,比例类似于3:1:6。
*客户的产生我没有用几个随机数来模拟,严格来说应该如此做的,为了调试方便让每隔1秒来一位客户。
**/
publicNumberMachine(NumberManagermm){
this.mm=mm;
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
newRunnable(){

@Override
publicvoidrun(){
intcustomerNum=getCustomerNum();
intcustomerType=r.nextInt(10);
switch(customerType)//根据随机数来创建具体客户类型
{
case0:
case1:
case2:
System.out.println("********第"+customerNum
+"号客户取票,该客户为快速客户!********");
NumberMachine.this.mm
.addExpressCustomer(customerNum);
break;
case3:
System.out.println("$$$$$$$$第"+customerNum
+"号客户取票,该客户为VIP客户!$$$$$$$$");
NumberMachine.this.mm.addVipCustomer(customerNum);
break;
default:
System.out.println("--------第"+customerNum
+"号客户取票,该客户为普通客户!--------");
NumberMachine.this.mm
.addCommenCustomer(customerNum);
break;
}
}

},0,1,TimeUnit.SECONDS);
}
}

窗口类(这个类是业务代码最多的类)

packagecom.interview.bank;

importjava.util.Random;
importjava.util.concurrent.Executors;
importjava.util.concurrent.ScheduledExecutorService;
importjava.util.concurrent.TimeUnit;

/**
*窗口类,该类有窗口的服务类型,窗口的号码,并且有一个数据管理器。
**/
publicclassServiceWindow{
privateCustomerTypetype=CustomerType.COMMEN;
privateNumberManagermm=null;
privateintwindowNum=0;
privatestaticRandomr=newRandom();

publicServiceWindow(CustomerTypetype,NumberManagermm,intwindowNum){
this.type=type;
this.mm=mm;
this.windowNum=windowNum;
}

/**
*窗口的开始方法,窗口一开始会启动一个线程,该线程是个调度线程,每隔1秒工作一次,拿到窗口对应类型客户号码,从而确定服务类型,调用服务方法。
*会根据窗口类型调用数据管理器的相应方法,得到对应容器中的客户号码。如果返回值为null说明该窗口空闲,并且非普通窗口可以转为普通客户服务。
*即先那对应类型客户号码,根据号码是否为null确定服务类型,若需要将服务类型转为普通就调用changeForCommen,
*否则选择用其对应的,serverForCommen、serverForExpress和serverForVip中的一个。
**/
publicvoidstart(){
ScheduledExecutorServicetimer=Executors
.newSingleThreadScheduledExecutor();
timer.scheduleAtFixedRate(newRunnable(){

@Override
publicvoidrun()//“消费”掉号码的线程
{
IntegercustomerNum=null;

System.out.println(type+"窗口"+windowNum+"尝试获取"+type
+"客户");
switch(ServiceWindow.this.type){
caseCOMMEN:
customerNum=mm.removeCommenCustomer();
serverForCommen(customerNum);
break;
caseEXPRESS:
customerNum=mm.removeExpressCustomer();
if(customerNum==null){
customerNum=changeForCommen();
serverForCommen(customerNum);
}else
serverForExpress(customerNum);
break;
caseVIP:
customerNum=mm.removeVipCustomer();
if(customerNum==null){
customerNum=changeForCommen();
serverForCommen(customerNum);
}else
serverForVip(customerNum);
break;
default:
break;
}
}

},0,1,TimeUnit.SECONDS);
}

/**
*转换服务类型方法,如果非普通窗口空闲(拿到对象应客户号码池中的号码为null),就调用该方法暂时转而为普通客户服务。
**/
privateIntegerchangeForCommen(){
IntegercustomerNum=null;
System.out.println(type+"窗口"+windowNum+"获取"+type
+"客户失败,尝试获取普通用户");
customerNum=mm.removeCommenCustomer();
returncustomerNum;
}

/**
*为快速客户服务的方法。(本来准备和VIP服务方法合在一起,但因为服务时间不一样所以不得不写成两个方法)
*其中除了打印提示信息,该方法主体就是线程休眠,用该休眠时间替代服务过程的时间,所以该方法是个阻塞方法,快速类型客户的服务时间固定为1秒。
**/
privatevoidserverForExpress(IntegercustomerNum){
System.out.println(type+"窗口"+windowNum+"正在为第"+customerNum
+"号*"+type+"*用户服务");
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(type+"窗口"+windowNum+"完成为第"+customerNum
+"号*"+type+"*用户的服务耗时1秒****");
}

/**
*为VIP客户服务的方法。(本来准备和快速客户服务方法合在一起,但因为服务时间不一样所以不得不写成两个方法)
*其中除了打印提示信息,该方法主体就是线程休眠
*,用该休眠时间替代服务过程的时间,所以该方法是个阻塞方法,VIP类型客户和普通客户一样,休眠时间是用随机数生成器产生的1-10秒之间。
**/
privatevoidserverForVip(IntegercustomerNum){
longserviceTime=(r.nextInt(10)+1)*1000;
System.out.println(type+"窗口"+windowNum+"正在为第"+customerNum
+"号$"+type+"$用户服务");
try{
Thread.sleep((r.nextInt(10)+1)*1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(type+"窗口"+windowNum+"完成为第"+customerNum
+"号$"+type+"$用户的服务耗时"+serviceTime/1000+"秒$$$$");
}

/**
*为普通客户服务的方法。先判断是否拿到普通客户号码,若没有线程休眠1秒,若有则为普通客户服务。
*其中除了打印提示信息,该方法主体就是线程休眠,用该休眠时间替代服务过程的时间
*,所以该方法是个阻塞方法,普通类型客户和VIP客户一样,休眠时间是用随机数生成器产生的1-10秒之间。
**/
privatevoidserverForCommen(IntegercustomerNum){
if(customerNum==null){
System.out.println(type+"窗口"+windowNum+"获取普通用户失败,休息1秒");
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}else{
longserviceTime=(r.nextInt(10)+1)*1000;
System.out.println(type+"窗口"+windowNum+"正在为第"+customerNum
+"号-普通-客户服务");
try{
Thread.sleep(serviceTime);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(type+"窗口"+windowNum+"完成为第"+customerNum
+"号"+"-普通-用户的服务耗时"+serviceTime/1000+"秒----");
}
}
}


号码管理类

packagecom.interview.bank;

importjava.util.LinkedList;

/**
*号码管理器类,统一管理客户号码的存贮和移除,由于有三种客户类型所以用三个不同的容器来装。
*由于每个容器都涉及多线程的并发访问,所以要加同步。(这里加的同步需要说明一下,个人觉得比较合理的方式是将监视器放在并发访问资源上,即三个容器上。
*但是这里为了代码简练就写成同步方法,即将锁的监视器绑定在调用方法的对象上(this)。而为了保证同步,该程序必须确保操作资源的对象是同一个,即单例。
*但同时这样做相当加大了锁的粒度,从集合对象到整个NumberManager对象,也就是说不同类型的客户并发操作也是互斥的,这样降低了程序效率。
*因为客户类型少并且同步方法简单而且快速和VIP客户窗口也经常进入到普通客户同步块中,所以不是很明显。而且最主要的是往集合中添加号码时,我采用的是统一编号,
*而不是每种客户类型单独编一组号
*,所以添加号码的线程锁监视器确实是在NumberManager对象上,这样为了同步就必须用到锁的嵌套,外面是大锁NumberManager对象,
*里面为了让取出号码线程不冲突还要加一个集合对象,拿到两个锁才进行添加号码操作,这样既复杂也容易导致死锁,所以就采用这种稍微牺牲效率的做法。)
*容器用了JDK1.6才有的LinkedList的pollFirst方法,防止NoSuchElementException的发生。
**/
publicclassNumberManager{
privateLinkedList<Integer>commonList=newLinkedList<Integer>();
privateLinkedList<Integer>expressList=newLinkedList<Integer>();
privateLinkedList<Integer>vipList=newLinkedList<Integer>();

/**
*管理器的单例模式
**/
privateNumberManager(){
}

privatestaticNumberManagermm=newNumberManager();

publicstaticNumberManagergetInstance(){
returnmm;
}

/**
*往普通客户号码容器中添加元素。
**/
publicsynchronizedvoidaddCommenCustomer(intcustomerNum){
commonList.add(customerNum);
}

/**
*往快速客户号码容器中添加元素。
**/
publicsynchronizedvoidaddExpressCustomer(intcustomerNum){
expressList.add(customerNum);
}

/**
*往VIP客户号码容器中添加元素。
**/
publicsynchronizedvoidaddVipCustomer(intcustomerNum){
vipList.add(customerNum);
}

/**
*从普通客户号码容器中取出元素,若没有返回null。
**/
publicsynchronizedIntegerremoveCommenCustomer(){
returncommonList.pollFirst();
}

/**
*从快速客户号码容器中取出元素,若没有返回null。
**/
publicsynchronizedIntegerremoveExpressCustomer(){
returnexpressList.pollFirst();
}

/**
*从VIP客户号码容器中取出元素,若没有返回null。
**/
publicsynchronizedIntegerremoveVipCustomer(){
returnvipList.pollFirst();
}
}


主类-----银行控制系统类

packagecom.interview.bank;

/**
*银行控制系统类,也是整个工程的主方法类。该类为了不让其它对象操作,把他设计成单例,且不对外提供单例的对象访问。
**/
publicclassBankSystem
{
privateBankSystem(){}
privatestaticBankSystembs=newBankSystem();
/**
*工程的主方法,先获得数据管理器的单例对象(因为其它对象都要依赖于该对象来生成)。然后开辟6个服务窗口并且让它们开始工作,在开辟一个取号机(取号机一存在就会自动开始工作)。
**/
publicstaticvoidmain(String[]args)/
{
NumberManagermm=NumberManager.getInstance();
intwindowNum=1;
newServiceWindow(CustomerType.COMMEN,mm,windowNum++).start();
newServiceWindow(CustomerType.COMMEN,mm,windowNum++).start();
newServiceWindow(CustomerType.COMMEN,mm,windowNum++).start();
newServiceWindow(CustomerType.COMMEN,mm,windowNum++).start();
newServiceWindow(CustomerType.EXPRESS,mm,windowNum++).start();
newServiceWindow(CustomerType.VIP,mm,windowNum++).start();
newNumberMachine(mm);
}
}


-------android培训、java培训、期待与您交流!
----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: