---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------
银行业务调度系统的项目需求
面向对象的分析与设计:
1.有三种对应类型的客户:VIP客户,普通客户,快速客户 ,异步随机生成各种类型的客户,各类型客户在其对应窗口按顺序依次办理业务 。
首先,每一个客户其实就是由银行的一个取号机器产生号码的方式来表示的。所以,想到要有一个号码管理器对象,让这个对象不断地产生号码,就等于随机生成了客户。
其次,由于有三类客户,每类客户的号码编排都是完全独立的,所以,本系统一共要产生三个号码管理器对象,各自管理一类用户的排队号码。这三个号码管理器对象统一由一个号码机器进行管理,这个号码机器在整个系统中始终只能有一个,所以,它要被设计成单例。
2.各类型客户在其对应窗口按顺序依次办理业务 ,准确地说,应该是窗口依次叫号。
各个窗口怎么知道该叫哪一个号了呢?它一定是问的相应的号码管理器,即服务窗口每次找号码管理器获取当前要被服务的号码。
简单的类内部逻辑分析:
号码机器(管理3个号码管理器对象,自身设计成单例)
号码管理器对象(产生新号码,为服务窗口提供需要服务的号码)
服务窗口(内部定义线程循环调用三种方法,定义三种方法对三种不同客户进行服务)
号码管理器类
1 package cn.itheima.banksystem; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class NumberManager { 7 private int lastNumber = 1;//定义变量上一次返回的号码 8 private List<Integer> queueNumbers = new ArrayList<Integer>();//定义一个集合存放队列中的号码 9 //两个不同的线程访问相同的数据,产生新号码的方法和取服务号码的方法要加上synchronized关键字,修饰成同步函数 10 public synchronized Integer generateNewNumber(){//定义产生新号码方法 11 queueNumbers.add(lastNumber); 12 return lastNumber++; 13 } 14 15 public synchronized Integer fetchNumber(){//服务窗口来取服务号码,ArrayList有序集合先进先出 16 if(queueNumbers.size()>0){//防止集合为空的情况下角标越界 17 return (Integer)queueNumbers.remove(0); 18 }else{ 19 return null; 20 } 21 } 22 }
号码机器类
1 package cn.itheima.banksystem; 2 public class NumberMachine { 3 //写单例最后分两步走,先写成普通类,再把类设计成单例。 4 private NumberMachine(){} 5 private static NumberMachine instance = new NumberMachine(); 6 public static NumberMachine getInstance(){ 7 return instance; 8 } 9 //内部管理三种客户号码管理器对象 10 private NumberManager commonManager = new NumberManager(); 11 private NumberManager expressManager = new NumberManager(); 12 private NumberManager vipManager = new NumberManager(); 13 public NumberManager getCommonManager() { 14 return commonManager; 15 } 16 public NumberManager getExpressManager() { 17 return expressManager; 18 } 19 public NumberManager getVipManager() { 20 return vipManager; 21 } 22 }
服务窗口类
1 package cn.itheima.banksystem; 2 import java.util.Random; 3 import java.util.concurrent.Executors; 4 /** 5 * 没有把VIP窗口和快速窗口做成子类,是因为实际业务中的普通窗口可以随时被设置为VIP窗口和快速窗口。 6 **/ 7 public class ServiceWindow { 8 private CustomerType type = CustomerType.COMMON; 9 private int number = 1; 10 11 public CustomerType getType() { 12 return type; 13 } 14 15 public void setType(CustomerType type) { 16 this.type = type; 17 } 18 19 public void setNumber(int number){ 20 this.number = number; 21 } 22 23 public void start(){//newSingleThreadExecutor创建一个使用单个worker线程的 Executor,以无界队列方式来运行该线程。 24 Executors.newSingleThreadExecutor().execute( 25 new Runnable(){ 26 public void run(){ 27 //下面这种写法的运行效率低,最好是把while放在case下面 28 while(true){ 29 switch(type){ 30 case COMMON: 31 commonService(); 32 break; 33 case EXPRESS: 34 expressService(); 35 break; 36 case VIP: 37 vipService(); 38 break; 39 } 40 } 41 } 42 } 43 ); 44 } 45 private void commonService(){ 46 //定义窗口名 47 String windowName = "第" + number + "号" + type + "窗口"; 48 System.out.println(windowName + "开始获取普通任务!"); 49 //获取服务号码 50 Integer serviceNumber = NumberMachine.getInstance().getCommonManager().fetchNumber(); 51 if(serviceNumber != null ){ 52 System.out.println(windowName + "开始为第" + serviceNumber + "号普通客户服务"); 53 int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME; 54 //定义服务时间,随机范围(1-9000+最小服务时间1000=1-10000毫秒) 55 int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME; 56 57 try { 58 Thread.sleep(serviceTime); 59 } catch (InterruptedException e) { 60 e.printStackTrace(); 61 } 62 System.out.println(windowName + "完成为第" + serviceNumber + "号普通客户服务,总共耗时" + serviceTime/1000 + "秒"); 63 }else{ 64 System.out.println(windowName + "没有取到普通任务,正在空闲一秒"); 65 try { 66 Thread.sleep(1000); 67 } catch (InterruptedException e) { 68 e.printStackTrace(); 69 } 70 } 71 } 72 73 private void expressService(){ 74 Integer serviceNumber = NumberMachine.getInstance().getExpressManager().fetchNumber(); 75 String windowName = "第" + number + "号" + type + "窗口"; 76 System.out.println(windowName + "开始获取快速任务!"); 77 if(serviceNumber !=null){ 78 System.out.println(windowName + "开始为第" + serviceNumber + "号快速客户服务"); 79 int serviceTime = Constants.MIN_SERVICE_TIME; 80 try { 81 Thread.sleep(serviceTime); 82 } catch (InterruptedException e) { 83 e.printStackTrace(); 84 } 85 System.out.println(windowName + "完成为第" + serviceNumber + "号快速客户服务,总共耗时" + serviceTime/1000 + "秒"); 86 }else{ 87 System.out.println(windowName + "没有取到快速任务!"); 88 commonService();//快速窗口没取到快速任务就为普通客户服务 89 } 90 } 91 92 private void vipService(){ 93 Integer serviceNumber = NumberMachine.getInstance().getVipManager().fetchNumber(); 94 String windowName = "第" + number + "号" + type + "窗口"; 95 System.out.println(windowName + "开始获取VIP任务!"); 96 if(serviceNumber !=null){ 97 System.out.println(windowName + "开始为第" + serviceNumber + "号VIP客户服务"); 98 int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME; 99 int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME; 100 try { 101 Thread.sleep(serviceTime); 102 } catch (InterruptedException e) { 103 e.printStackTrace(); 104 } 105 System.out.println(windowName + "完成为第" + serviceNumber + "号VIP客户服务,总共耗时" + serviceTime/1000 + "秒"); 106 }else{ 107 System.out.println(windowName + "没有取到VIP任务!"); 108 commonService();//VIP窗口没取到VIP任务就为普通客户服务 109 } 110 } 111 }
void |
execute(Runnable command) 在未来某个时间执行给定的命令。 |
static ExecutorService |
newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 |
枚举定义定义三种窗口类型
1 package cn.itheima.banksystem; 2 //枚举类定义三种窗口类型 3 public enum CustomerType { 4 COMMON,EXPRESS,VIP; 5 public String toString(){ 6 String name = null; 7 switch(this){ 8 case COMMON: 9 name = "普通"; 10 break; 11 case EXPRESS: 12 name = "快速"; 13 break; 14 case VIP: 15 name = name();//enum类中name()返回此枚举常量的名称。 16 break; 17 } 18 return name; 19 } 20 }
常量类
1 package cn.itheima.banksystem; 2 //定义常量类 3 public class Constants { 4 public static int MAX_SERVICE_TIME = 10000; //最长服务时间10秒! 5 public static int MIN_SERVICE_TIME = 1000; //最短服务时间1秒! 6 7 /*每个普通窗口服务一个客户的平均时间为5秒,一共有4个这样的窗口,也就是说银行的所有普通窗口合起来平均1.25秒内可以服务完一个普通客户,再加上快速窗口和VIP窗口也可以服务普通客户,所以1秒钟产生一个普通客户比较合理,*/ 8 public static int COMMON_CUSTOMER_INTERVAL_TIME = 1000; 9 }
主函数入口
1 package cn.itheima.banksystem; 2 import java.util.concurrent.Executors; 3 import java.util.concurrent.TimeUnit; 4 public class MainClass { 5 6 public static void main(String[] args) { 7 //产生4个普通窗口 8 for(int i=1;i<5;i++){ 9 ServiceWindow window = new ServiceWindow(); 10 window.setNumber(i); 11 window.start(); 12 } 13 14 //产生1个快速窗口 15 ServiceWindow expressWindow = new ServiceWindow(); 16 expressWindow.setType(CustomerType.EXPRESS); 17 expressWindow.start(); 18 19 //产生1个VIP窗口 20 ServiceWindow vipWindow = new ServiceWindow(); 21 vipWindow.setType(CustomerType.VIP); 22 vipWindow.start(); 23 24 //普通客户拿号 25 //创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。参数:corePoolSize-池中所保存的线程数,即使线程是空闲的也包括在内。 26 Executors.newScheduledThreadPool(1).scheduleAtFixedRate( 27 new Runnable(){ 28 public void run(){ 29 //找号码机器生产新号码 30 Integer serviceNumber = NumberMachine.getInstance().getCommonManager().generateNewNumber(); 31 System.out.println("第" + serviceNumber + "号普通客户正在等待服务!"); 32 } 33 }, 34 0, 35 Constants.COMMON_CUSTOMER_INTERVAL_TIME, 36 TimeUnit.MILLISECONDS); 37 /*scheduleAtFixedRatescheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 38 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期 39 参数列表 40 command - 要执行的任务 41 initialDelay - 首次执行的延迟时间 42 period - 连续执行之间的周期 43 unit - initialDelay 和 period 参数的时间单位*/ 44 45 //快速客户拿号 46 Executors.newScheduledThreadPool(1).scheduleAtFixedRate( 47 new Runnable(){ 48 public void run(){ 49 Integer serviceNumber = NumberMachine.getInstance().getExpressManager().generateNewNumber(); 50 System.out.println("第" + serviceNumber + "号快速客户正在等待服务!"); 51 } 52 }, 53 0, 54 Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2, 55 TimeUnit.SECONDS); 56 57 //VIP客户拿号 58 Executors.newScheduledThreadPool(1).scheduleAtFixedRate( 59 new Runnable(){ 60 public void run(){ 61 Integer serviceNumber = NumberMachine.getInstance().getVipManager().generateNewNumber(); 62 System.out.println("第" + serviceNumber + "号VIP客户正在等待服务!"); 63 } 64 }, 65 0, 66 Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6, 67 TimeUnit.SECONDS); 68 } 69 70 }
static ScheduledExecutorService |
newScheduledThreadPool(int corePoolSize) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
ScheduledFuture<?> |
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。 |
- 参数:
command
- 要执行的任务initialDelay
- 首次执行的延迟时间period
- 连续执行之间的周期unit
- initialDelay 和 period 参数的时间单位
总结:
用到的主要知识点有面向对象的分析,单例模式,多线程以及新特性线程池,集合,枚举,异常处理机制。其中面向对象的分析是设计的灵魂。