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

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

Ø VIP6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。
Ø 随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。
Ø 不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。
 
该系统中,不变的是号码产生器。号码产生器中有三个号码控制器产生三种号码。只有一个机器,故为单例模式。对于窗口,有开始服务程序。Jdk1.6版本中,应用线程池。


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

代码如下: 

  1. package com.interview.bank;  
  2.   
  3. /** 
  4.  * 服务类型的枚举类,有三种普通、快速和VIP。 其中每个元素都重写了其toString方法。 
  5.  * */  
  6. public enum CustomerType {  
  7.     /** 
  8.      * 普通类型 
  9.      * */  
  10.     COMMEN {  
  11.         public String toString() {  
  12.             return "普通";  
  13.         }  
  14.     },  
  15.     /** 
  16.      * 快速类型 
  17.      * */  
  18.     EXPRESS {  
  19.         public String toString() {  
  20.             return "快速";  
  21.         }  
  22.     },  
  23.     /** 
  24.      * VIP类型 
  25.      * */  
  26.     VIP {  
  27.         public String toString() {  
  28.             return name();  
  29.         }  
  30.     };  
  31. }  
取号机类

  1. package com.interview.bank;  
  2.   
  3. import java.util.Random;  
  4. import java.util.concurrent.Executors;  
  5. import java.util.concurrent.TimeUnit;  
  6.   
  7. /** 
  8.  * 取号机器类,该类主要负责号码对象的产生和确定号码对象类型(客户类型)。 
  9.  * */  
  10. public class NumberMachine {  
  11.     private static Random r = new Random();  
  12.     private static int customerNum = 0;  
  13.     private NumberManager mm = null;  
  14.   
  15.     private static int getCustomerNum() {  
  16.         return (++customerNum);  
  17.     }  
  18.   
  19.     /** 
  20.      * 取号机的构造方法。由于要操作渠道的号,所以需要一个专门操作客户号码的号码管理器类对象。 
  21.      * 并且在取号机一出来就开始工作,即创建一个调度线程模拟客户的产生和客户选择的服务类型。 
  22.      * 这里有一个随机数生成器来模拟客户选择的服务类型。0-2表示是快速类型客户,3表示是VIP类型客户,4-9表示是普通类型客户,比例类似于3:1:6。 
  23.      * 客户的产生我没有用几个随机数来模拟,严格来说应该如此做的,为了调试方便让每隔1秒来一位客户。 
  24.      * */  
  25.     public NumberMachine(NumberManager mm) {  
  26.         this.mm = mm;  
  27.         Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(  
  28.                 new Runnable() {  
  29.   
  30.                     @Override  
  31.                     public void run() {  
  32.                         int customerNum = getCustomerNum();  
  33.                         int customerType = r.nextInt(10);  
  34.                         switch (customerType)// 根据随机数来创建具体客户类型  
  35.                         {  
  36.                         case 0:  
  37.                         case 1:  
  38.                         case 2:  
  39.                             System.out.println("********第" + customerNum  
  40.                                     + "号客户取票,该客户为快速客户!********");  
  41.                             NumberMachine.this.mm  
  42.                                     .addExpressCustomer(customerNum);  
  43.                             break;  
  44.                         case 3:  
  45.                             System.out.println("$$$$$$$$第" + customerNum  
  46.                                     + "号客户取票,该客户为VIP客户!$$$$$$$$");  
  47.                             NumberMachine.this.mm.addVipCustomer(customerNum);  
  48.                             break;  
  49.                         default:  
  50.                             System.out.println("--------第" + customerNum  
  51.                                     + "号客户取票,该客户为普通客户!--------");  
  52.                             NumberMachine.this.mm  
  53.                                     .addCommenCustomer(customerNum);  
  54.                             break;  
  55.                         }  
  56.                     }  
  57.   
  58.                 }, 01, TimeUnit.SECONDS);  
  59.     }  
  60. }  
窗口类(这个类是业务代码最多的类)

  1. package com.interview.bank;  
  2.   
  3. import java.util.Random;  
  4. import java.util.concurrent.Executors;  
  5. import java.util.concurrent.ScheduledExecutorService;  
  6. import java.util.concurrent.TimeUnit;  
  7.   
  8. /** 
  9.  * 窗口类,该类有窗口的服务类型,窗口的号码,并且有一个数据管理器。 
  10.  * */  
  11. public class ServiceWindow {  
  12.     private CustomerType type = CustomerType.COMMEN;  
  13.     private NumberManager mm = null;  
  14.     private int windowNum = 0;  
  15.     private static Random r = new Random();  
  16.   
  17.     public ServiceWindow(CustomerType type, NumberManager mm, int windowNum) {  
  18.         this.type = type;  
  19.         this.mm = mm;  
  20.         this.windowNum = windowNum;  
  21.     }  
  22.   
  23.     /** 
  24.      * 窗口的开始方法,窗口一开始会启动一个线程,该线程是个调度线程,每隔1秒工作一次,拿到窗口对应类型客户号码,从而确定服务类型,调用服务方法。 
  25.      * 会根据窗口类型调用数据管理器的相应方法,得到对应容器中的客户号码。如果返回值为null说明该窗口空闲,并且非普通窗口可以转为普通客户服务。 
  26.      * 即先那对应类型客户号码,根据号码是否为null确定服务类型,若需要将服务类型转为普通就调用changeForCommen, 
  27.      * 否则选择用其对应的,serverForCommen、serverForExpress和serverForVip中的一个。 
  28.      * */  
  29.     public void start() {  
  30.         ScheduledExecutorService timer = Executors  
  31.                 .newSingleThreadScheduledExecutor();  
  32.         timer.scheduleAtFixedRate(new Runnable() {  
  33.   
  34.             @Override  
  35.             public void run()// “消费”掉号码的线程  
  36.             {  
  37.                 Integer customerNum = null;  
  38.   
  39.                 System.out.println(type + "窗口" + windowNum + "尝试获取" + type  
  40.                         + "客户");  
  41.                 switch (ServiceWindow.this.type) {  
  42.                 case COMMEN:  
  43.                     customerNum = mm.removeCommenCustomer();  
  44.                     serverForCommen(customerNum);  
  45.                     break;  
  46.                 case EXPRESS:  
  47.                     customerNum = mm.removeExpressCustomer();  
  48.                     if (customerNum == null) {  
  49.                         customerNum = changeForCommen();  
  50.                         serverForCommen(customerNum);  
  51.                     } else  
  52.                         serverForExpress(customerNum);  
  53.                     break;  
  54.                 case VIP:  
  55.                     customerNum = mm.removeVipCustomer();  
  56.                     if (customerNum == null) {  
  57.                         customerNum = changeForCommen();  
  58.                         serverForCommen(customerNum);  
  59.                     } else  
  60.                         serverForVip(customerNum);  
  61.                     break;  
  62.                 default:  
  63.                     break;  
  64.                 }  
  65.             }  
  66.   
  67.         }, 01, TimeUnit.SECONDS);  
  68.     }  
  69.   
  70.     /** 
  71.      * 转换服务类型方法,如果非普通窗口空闲(拿到对象应客户号码池中的号码为null),就调用该方法暂时转而为普通客户服务。 
  72.      * */  
  73.     private Integer changeForCommen() {  
  74.         Integer customerNum = null;  
  75.         System.out.println(type + "窗口" + windowNum + "获取" + type  
  76.                 + "客户失败,尝试获取普通用户");  
  77.         customerNum = mm.removeCommenCustomer();  
  78.         return customerNum;  
  79.     }  
  80.   
  81.     /** 
  82.      * 为快速客户服务的方法。(本来准备和VIP服务方法合在一起,但因为服务时间不一样所以不得不写成两个方法) 
  83.      * 其中除了打印提示信息,该方法主体就是线程休眠,用该休眠时间替代服务过程的时间,所以该方法是个阻塞方法,快速类型客户的服务时间固定为1秒。 
  84.      * */  
  85.     private void serverForExpress(Integer customerNum) {  
  86.         System.out.println(type + "窗口" + windowNum + "正在为第" + customerNum  
  87.                 + "号*" + type + "*用户服务");  
  88.         try {  
  89.             Thread.sleep(1000);  
  90.         } catch (InterruptedException e) {  
  91.             e.printStackTrace();  
  92.         }  
  93.         System.out.println(type + "窗口" + windowNum + "完成为第" + customerNum  
  94.                 + "号*" + type + "*用户的服务耗时1秒****");  
  95.     }  
  96.   
  97.     /** 
  98.      * 为VIP客户服务的方法。(本来准备和快速客户服务方法合在一起,但因为服务时间不一样所以不得不写成两个方法) 
  99.      * 其中除了打印提示信息,该方法主体就是线程休眠 
  100.      * ,用该休眠时间替代服务过程的时间,所以该方法是个阻塞方法,VIP类型客户和普通客户一样,休眠时间是用随机数生成器产生的1-10秒之间。 
  101.      * */  
  102.     private void serverForVip(Integer customerNum) {  
  103.         long serviceTime = (r.nextInt(10) + 1) * 1000;  
  104.         System.out.println(type + "窗口" + windowNum + "正在为第" + customerNum  
  105.                 + "号$" + type + "$用户服务");  
  106.         try {  
  107.             Thread.sleep((r.nextInt(10) + 1) * 1000);  
  108.         } catch (InterruptedException e) {  
  109.             e.printStackTrace();  
  110.         }  
  111.         System.out.println(type + "窗口" + windowNum + "完成为第" + customerNum  
  112.                 + "号$" + type + "$用户的服务耗时" + serviceTime / 1000 + "秒$$$$");  
  113.     }  
  114.   
  115.     /** 
  116.      * 为普通客户服务的方法。先判断是否拿到普通客户号码,若没有线程休眠1秒,若有则为普通客户服务。 
  117.      * 其中除了打印提示信息,该方法主体就是线程休眠,用该休眠时间替代服务过程的时间 
  118.      * ,所以该方法是个阻塞方法,普通类型客户和VIP客户一样,休眠时间是用随机数生成器产生的1-10秒之间。 
  119.      * */  
  120.     private void serverForCommen(Integer customerNum) {  
  121.         if (customerNum == null) {  
  122.             System.out.println(type + "窗口" + windowNum + "获取普通用户失败,休息1秒");  
  123.             try {  
  124.                 Thread.sleep(1000);  
  125.             } catch (InterruptedException e) {  
  126.                 e.printStackTrace();  
  127.             }  
  128.         } else {  
  129.             long serviceTime = (r.nextInt(10) + 1) * 1000;  
  130.             System.out.println(type + "窗口" + windowNum + "正在为第" + customerNum  
  131.                     + "号-普通-客户服务");  
  132.             try {  
  133.                 Thread.sleep(serviceTime);  
  134.             } catch (InterruptedException e) {  
  135.                 e.printStackTrace();  
  136.             }  
  137.             System.out.println(type + "窗口" + windowNum + "完成为第" + customerNum  
  138.                     + "号" + "-普通-用户的服务耗时" + serviceTime / 1000 + "秒----");  
  139.         }  
  140.     }  
  141. }  


号码管理类


  1. package com.interview.bank;  
  2.   
  3. import java.util.LinkedList;  
  4.   
  5. /** 
  6.  * 号码管理器类,统一管理客户号码的存贮和移除,由于有三种客户类型所以用三个不同的容器来装。 
  7.  * 由于每个容器都涉及多线程的并发访问,所以要加同步。(这里加的同步需要说明一下,个人觉得比较合理的方式是将监视器放在并发访问资源上,即三个容器上。 
  8.  * 但是这里为了代码简练就写成同步方法,即将锁的监视器绑定在调用方法的对象上(this)。而为了保证同步,该程序必须确保操作资源的对象是同一个,即单例。 
  9.  * 但同时这样做相当加大了锁的粒度,从集合对象到整个NumberManager对象,也就是说不同类型的客户并发操作也是互斥的,这样降低了程序效率。 
  10.  * 因为客户类型少并且同步方法简单而且快速和VIP客户窗口也经常进入到普通客户同步块中,所以不是很明显。而且最主要的是往集合中添加号码时,我采用的是统一编号, 
  11.  * 而不是每种客户类型单独编一组号 
  12.  * ,所以添加号码的线程锁监视器确实是在NumberManager对象上,这样为了同步就必须用到锁的嵌套,外面是大锁NumberManager对象, 
  13.  * 里面为了让取出号码线程不冲突还要加一个集合对象,拿到两个锁才进行添加号码操作,这样既复杂也容易导致死锁,所以就采用这种稍微牺牲效率的做法。) 
  14.  * 容器用了JDK1.6才有的LinkedList的pollFirst方法,防止NoSuchElementException的发生。 
  15.  * */  
  16. public class NumberManager {  
  17.     private LinkedList<Integer> commonList = new LinkedList<Integer>();  
  18.     private LinkedList<Integer> expressList = new LinkedList<Integer>();  
  19.     private LinkedList<Integer> vipList = new LinkedList<Integer>();  
  20.   
  21.     /** 
  22.      * 管理器的单例模式 
  23.      * */  
  24.     private NumberManager() {  
  25.     }  
  26.   
  27.     private static NumberManager mm = new NumberManager();  
  28.   
  29.     public static NumberManager getInstance() {  
  30.         return mm;  
  31.     }  
  32.   
  33.     /** 
  34.      * 往普通客户号码容器中添加元素。 
  35.      * */  
  36.     public synchronized void addCommenCustomer(int customerNum) {  
  37.         commonList.add(customerNum);  
  38.     }  
  39.   
  40.     /** 
  41.      * 往快速客户号码容器中添加元素。 
  42.      * */  
  43.     public synchronized void addExpressCustomer(int customerNum) {  
  44.         expressList.add(customerNum);  
  45.     }  
  46.   
  47.     /** 
  48.      * 往VIP客户号码容器中添加元素。 
  49.      * */  
  50.     public synchronized void addVipCustomer(int customerNum) {  
  51.         vipList.add(customerNum);  
  52.     }  
  53.   
  54.     /** 
  55.      * 从普通客户号码容器中取出元素,若没有返回null。 
  56.      * */  
  57.     public synchronized Integer removeCommenCustomer() {  
  58.         return commonList.pollFirst();  
  59.     }  
  60.   
  61.     /** 
  62.      * 从快速客户号码容器中取出元素,若没有返回null。 
  63.      * */  
  64.     public synchronized Integer removeExpressCustomer() {  
  65.         return expressList.pollFirst();  
  66.     }  
  67.   
  68.     /** 
  69.      * 从VIP客户号码容器中取出元素,若没有返回null。 
  70.      * */  
  71.     public synchronized Integer removeVipCustomer() {  
  72.         return vipList.pollFirst();  
  73.     }  
  74. }  
主类-----银行控制系统类

  1. package com.interview.bank;  
  2.   
  3. /** 
  4.  * 银行控制系统类,也是整个工程的主方法类。该类为了不让其它对象操作,把他设计成单例,且不对外提供单例的对象访问。 
  5.  * */  
  6. public class BankSystem  
  7. {  
  8.  private BankSystem(){}  
  9.  private static BankSystem bs = new BankSystem();  
  10.  /** 
  11.   * 工程的主方法,先获得数据管理器的单例对象(因为其它对象都要依赖于该对象来生成)。然后开辟6个服务窗口并且让它们开始工作,在开辟一个取号机(取号机一存在就会自动开始工作)。 
  12.   * */  
  13.  public static void main(String[] args)/  
  14.  {  
  15.   NumberManager mm = NumberManager.getInstance();  
  16.   int windowNum = 1;  
  17.   new ServiceWindow(CustomerType.COMMEN, mm,windowNum++).start();  
  18.   new ServiceWindow(CustomerType.COMMEN, mm,windowNum++).start();  
  19.   new ServiceWindow(CustomerType.COMMEN, mm,windowNum++).start();  
  20.   new ServiceWindow(CustomerType.COMMEN, mm,windowNum++).start();  
  21.   new ServiceWindow(CustomerType.EXPRESS, mm,windowNum++).start();  
  22.   new ServiceWindow(CustomerType.VIP, mm,windowNum++).start();  
  23.   new NumberMachine(mm);  
  24.  }  
  25. }  
------- Windows Phone 7手机开发.Net培训、期待与您交流! -------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值