【黑马程序员】Java7K面试题21:银行业务调度系统

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------


一、银行业务调度系统

模拟实现银行业务调度系统逻辑,具体需求如下:

1银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口

2有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)

3异步随机生成各种类型的客户,生成各类型用户的概率比例为:VIP客户:普通客户:快速客户 =1:6:3

4客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)

5各类型客户在其对应窗口按顺序依次办理业务

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

7随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置

8不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。


二、分析问题

1、三种类型的客户:VIP、普通、快速,异步随机生成各种类型的客户:

1)每一个客户其实就是由银行的一个取号器产生号码的方式来表示的,所以要有一个号码管理器对象,让这个对象不断地产生号码,就等于随机生成了客户;

2)由于有三类客户,每类客户的号码编排都是完全独立的,所以本系统一共要产生三个号码管理器对象,各自管理一类用户的排队号码;

3)这三个号码管理器对象统一由一个号码机器进行管理,这个号码机器在整个系统中始终只能有一个,需要被设计成单例。

 

2、各类型客户在其对应窗口按顺序依次办理业务,其实就是窗口依次叫号:

各个窗口想知道该叫哪一个号了,就需要询问相应的号码管理器,即服务窗口每次找号码管理器获取当前要被服务的号码。

 

3、通过以上分析,可以画一个类图,如下所示:

 

4、本案例中需要用到的思想与方法:

1)使用面向对象的思维来分析和解决问题,重要经验:谁拥有数据,谁就对外提供操作这些数据的方法;比如本案例中的生成号码,取号码的方法都应该放在号码管理器的类中;

2)在项目中,可以单独建一个Constants类,将静态常量放入其中供程序调用;

3)空间与逻辑性的关系,要多画图,多动手,不能仅凭空想象;

4)程序中的对象数量如果是确定的,可以用 JDK1.5中的枚举,如本案例中的三种客户类型;

5)异步随机生成各种类型的客户,需要使用 JDK1.5中调度线程池的功能。

 

三、代码的具体实现

1NumberManager

1)定义一个用于存储上一个客户号码的成员变量和用于存储所有等待服务的客户号码的队列集合;

2)定义一个产生新号码的方法和获取马上要为之服务的号码的方法,这两个方法被不同的线程操作了相同的数据,所以,要进行同步。

//NumberManager类用于表示生成号码和获取服务号的号码管理器
package blog.itheima;
import java.util.ArrayList;
import java.util.List;

public class NumberManager {
	private int lastNumber = 1;
	private List<Integer> queueNumber = new ArrayList<Integer>();
	//List<Integer>,这里不用ArrayList的原因:面向接口编程,后面可以自由设定
	//变量类型尽量面向父级或者接口的上一级,显得更灵活
	
	//由于生成号和取号的变量是同一个,多线程操作时会有安全问题,要互斥
	//取号和生成号时,为了防止无客户,报空指针异常,可以用整形对象Integer
	//generateNewNumber:定义一个生成新号码的方法
	public synchronized /*int*/Integer generateNewNumber(){
		//把生成的号码存入集合之中
		queueNumber.add(lastNumber);
		//每生成一个号,就自增一次,记住本类号码管理器的服务号
		return lastNumber++;
	}
	
	//取号要排队,每次取出的就是生成的第一个号码,也要互斥
	public synchronized /*int*/ Integer fetchServiceNumber(){
		if(queueNumber.size()>0){
			return queueNumber.remove(0);
		}
		//如果集合里面还没有装入元素,返回null
		return null;
	}
}

2NumberMachine类

1)定义三个成员变量指向三个NumberManager对象,分别表示普通、快速和VIP客户的号码管理器

2)定义三个对应的方法来返回这三个NumberManager对象

3)将NumberMachine类设计成单例。

//NumberMachine类相当于银行的取号管理机
package blog.itheima;
public class NumberMachine {
	
	//取号机只有一个,设计成单例模式,饿汗式
	private NumberMachine(){}
	private static NumberMachine instance = new NumberMachine();
	public  static NumberMachine getInstance(){
		return instance;
	}
	
	//创建三种类型的号码管理器对象
	private NumberManager commonManager = new NumberManager();
	private NumberManager expressManager = new NumberManager();
	private NumberManager vipManager = new NumberManager();
	
	//获取普通客户的号码管理器
	public NumberManager getCommonManager() {
		return commonManager;
	}
	//获取快速客户的号码管理器
	public NumberManager getExpressManager() {
		return expressManager;
	}
	//获取VIP客户的号码管理器
	public NumberManager getVipManager() {
		return vipManager;
	}
}

3CustomerType枚举类

1)系统中有三种类型的客户,创建一个枚举类,其中定义三个成员分别表示三种类型的客户

2)重写toString方法,返回类型的中文名称。

//定义枚举类,三个成员分别表示三种类型的客户
package blog.itheima;
public enum CustomerType {
	COMMON,EXPRESS,VIP;
	
	public String toString(){
		switch(this){
		//switch的数据类型只能是byte,short,int,char和枚举
		case COMMON:
			return "普通";
		case EXPRESS:
			return "快速";
		case VIP:
			return name();
		}
		return null;
	}
}

4Constants

定义三个常量:MAX_SERVICE_TIMEMIN_SERVICE_TIMECOMMON_CUSTOMER_INTERVAL_TIME

package blog.itheima;
public class Constants { //Constants:常量、常数
	public static int MAX_SERVICE_TIME = 10000;	//10秒!
	public static int MIN_SERVICE_TIME = 1000;	//1秒!
	public static int COMMON_CUSTOMER_INTERVAL = 1;//interval:间隔
}
/*每个普通窗口服务一个客户的平均时间为5秒,一共有4个这样的窗口,
 * 也就是说银行的所有普通窗口合起来平均1.25秒内可以服务完一个普通客户,
 * 再加上快速窗口和VIP窗口也可以服务普通客户,所以1秒钟产生一个普通客户比较合理
 */	

5ServiceWindow类

1)定义一个start方法,内部启动一个线程,根据服务窗口的类别分别循环调用三个不同的方法

2)定义三个方法分别对三种客户进行服务,为了观察运行效果,应详细打印出其中的细节信息。

//ServiceWindow类是模拟窗口叫号,并对相应的客户进行服务的类
package blog.itheima;
import java.util.Random;
import java.util.concurrent.Executors;

public class ServiceWindow {
	/**
	 没有把VIP窗口和快速窗口做成子类,
	 是因为实际业务中的普通窗口可以随时被设置为VIP窗口和快速窗口。
	*/
	//初始化窗口默认为1号
	private int windowId = 1;
	//初始化客户类型,默认为普通客户
	private CustomerType type = CustomerType.COMMON;
	
	//初始化ServiceWindow时,获取窗口号
	public void setWindowId(int windowId) {
		this.windowId = windowId;
	}
	//初始化ServiceWindow时,获取客户类型
	public void setType(CustomerType type) {
		this.type = type;
	}
	
	public void start(){
		//创建线程池,以后都要用1.5的新技术,此处是创建单一线程池,只为一类客户服务
		Executors.newSingleThreadExecutor().execute(new Runnable(){
			public void run() {
				while(true){ 
					//switch 比 if else的效率高一些
					switch(type){
					//如果客户类型是对应的客户,就调用相应的方法
						case COMMON:
							commonService();
							break;
						case EXPRESS:
							expressService();
							break;
						case VIP:
							vipService();
							break;
					}
				}
			}
		});
	}
	//普通客户窗口的叫号服务
	private void commonService() {
		//得到窗口序列号和服务类型
		String windowName = "第"+ windowId + "号" + type + "窗口";
		//通过单例调用,获取需要服务的普通客户号码
		Integer number = 
			NumberMachine.getInstance().getCommonManager().fetchServiceNumber();
		//fetchServiceNumber具有同步属性,打印语句放其下面执行,效果会更加流畅
		System.out.println(windowName+"正在获取普通任务");
		if(number!=null){
			//如果号码不为空,相应序号的窗口就开始为相应号码的客户进行服务
			System.out.println(windowName+"开始为第"+number+"号普通客户服务");	
			//记录服务开始时间
			long beginTime = System.currentTimeMillis();
			//客户服务需要的最大随机时间:9000毫秒
			int maxRandom = Constants.MAX_SERVICE_TIME-Constants.MIN_SERVICE_TIME;
			/*客户服务的随机时间为:
			new Random().nextInt(9000)为0-9999,加1和最小值即为101-10000毫秒*/
			long serveTime = new Random().nextInt(maxRandom)
													+1+Constants.MIN_SERVICE_TIME;
			try {//服务时间即是1-10秒的随机数
				Thread.sleep(serveTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long costTime = System.currentTimeMillis() - beginTime;
			System.out.println(windowName+"为第"+number
								+"号普通客户完成服务,服务时间"+costTime/1000+"秒");
		}else{
			System.out.println(windowName+"没有取到普通任务,先休息1秒钟");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	//快速客户窗口的叫号服务
	private void expressService() {
		String windowName = "第"+ windowId + "号" + type + "窗口";	
		System.out.println(windowName+"正在获取任务");
		Integer number = 
			NumberMachine.getInstance().getExpressManager().fetchServiceNumber();
		if(number!=null){
			System.out.println(windowName+"正在为第"+number+"个"+type+"客户服务");	
			long beginTime = System.currentTimeMillis();
			try {//快速客户的服务时间就是最小值
				Thread.sleep(Constants.MIN_SERVICE_TIME);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long costTime = System.currentTimeMillis() - beginTime;
			System.out.println(windowName+"为第"+number+"个"
								+type+"客户完成服务,服务时间"+costTime/1000+"秒");
		}else{
			System.out.println(windowName+"没有取到快速任务!");
			commonService();//如果没有快速任务,去取普通客户任务
		}
	}
	//VIP客户窗口的叫号服务
	private void vipService() {
		String windowName = "第"+ windowId + "号" + type + "窗口";	
		System.out.println(windowName+"正在获取任务");
		Integer number = 
			NumberMachine.getInstance().getVipManager().fetchServiceNumber();
		if(number!=null){
			System.out.println(windowName+"正在为第"+number+"个"+type+"客户服务");	
			long beginTime = System.currentTimeMillis();
			int maxRandom = Constants.MAX_SERVICE_TIME-Constants.MIN_SERVICE_TIME;
			long serveTime = new Random().nextInt(maxRandom)
					+1+Constants.MIN_SERVICE_TIME;
			try {//服务时间即是1-10秒的随机数
				Thread.sleep(serveTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long costTime = System.currentTimeMillis() - beginTime;
			System.out.println(windowName+"为第"+number+"个"
								+type+"客户完成服务,服务时间"+costTime/1000+"秒");
		}else{
			System.out.println(windowName+"没有取到VIP任务!");
			commonService();//如果没有VIP任务,去取普通客户任务
		}
	}
}

6MainClass类

1)用for循环创建出4个普通窗口,再创建出1个快速窗口和一个VIP窗口

2)再创建三个定时器,分别定时去创建新的普通客户号码、新的快速客户号码、新的VIP客户号码。

//MainClass类是程序运行时的主代码
package blog.itheima;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

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个快速窗口
		ServiceWindow expressWindow = new ServiceWindow();
		expressWindow.setType(CustomerType.EXPRESS);
		expressWindow.setWindowId(5);
		//不用设置窗口号,默认就是快速1窗口;也可设置快速窗口是5号窗口
		expressWindow.start();
	
		//产生1个VIP窗口		
		ServiceWindow vipWindow = new ServiceWindow();
		vipWindow.setType(CustomerType.VIP);
		vipWindow.setWindowId(6);
		//不用设置窗口号,默认就是VIP1窗口;也可设置VIP为6号窗口
		vipWindow.start();
	
		//普通客户拿号,调度线程池(即:定时器线程)
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){
					public void run() {
						//通过单例调用,生成普通客户号码
						Integer number = NumberMachine.getInstance()
										.getCommonManager().generateNewNumber();
						System.out.println("第"+ number +"号普通客户正在等待服务");
					}
				},
				0,
				Constants.COMMON_CUSTOMER_INTERVAL, 
				TimeUnit.SECONDS);
		
		//快速客户拿号
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){
					public void run() {
						Integer number = NumberMachine.getInstance()
										.getExpressManager().generateNewNumber();
						System.out.println("第" + number +"号快速客户正在等待服务");
					}
				},
				0,
				//VIP客户:普通客户:快速客户 =1:6:3,普通客户1秒来1个,快速2秒来1个
				Constants.COMMON_CUSTOMER_INTERVAL*2, 
				TimeUnit.SECONDS);
		
		//VIP客户拿号
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){
					public void run() {
						Integer number = NumberMachine.getInstance()
										.getVipManager().generateNewNumber();
						System.out.println("第" + number +"号VIP客户正在等待服务");
					}
				},
				0,
				//普通客户1秒来1个,VIP客户6秒来1个
				Constants.COMMON_CUSTOMER_INTERVAL*6, 
				TimeUnit.SECONDS);
	}
}

打印结果截取部分,如下所示:

1号普通窗口正在获取普通任务

1号普通窗口没有取到普通任务,先休息1秒钟

5号快速窗口正在获取任务

5号快速窗口没有取到快速任务!

5号快速窗口正在获取普通任务

5号快速窗口没有取到普通任务,先休息1秒钟

2号普通窗口正在获取普通任务

2号普通窗口没有取到普通任务,先休息1秒钟

3号普通窗口正在获取普通任务

3号普通窗口没有取到普通任务,先休息1秒钟

4号普通窗口正在获取普通任务

4号普通窗口没有取到普通任务,先休息1秒钟

6VIP窗口正在获取任务

6VIP窗口没有取到VIP任务!

6VIP窗口正在获取普通任务

6VIP窗口没有取到普通任务,先休息1秒钟

1号普通客户正在等待服务

1号快速客户正在等待服务

1VIP客户正在等待服务

1号普通窗口正在获取普通任务

1号普通窗口开始为第1号普通客户服务

2号普通窗口正在获取普通任务

2号普通窗口没有取到普通任务,先休息1秒钟

5号快速窗口正在获取任务

5号快速窗口正在为第1个快速客户服务

6VIP窗口正在获取任务

3号普通窗口正在获取普通任务

3号普通窗口没有取到普通任务,先休息1秒钟

6VIP窗口正在为第1VIP客户服务

4号普通窗口正在获取普通任务

4号普通窗口没有取到普通任务,先休息1秒钟

2号普通客户正在等待服务

2号普通窗口正在获取普通任务

2号普通窗口开始为第2号普通客户服务

5号快速窗口为第1个快速客户完成服务,服务时间1

5号快速窗口正在获取任务

5号快速窗口没有取到快速任务!

5号快速窗口正在获取普通任务

5号快速窗口没有取到普通任务,先休息1秒钟

4号普通窗口正在获取普通任务

4号普通窗口没有取到普通任务,先休息1秒钟

3号普通窗口正在获取普通任务

3号普通窗口没有取到普通任务,先休息1秒钟

3号普通客户正在等待服务

2号快速客户正在等待服务

5号快速窗口正在获取任务

5号快速窗口正在为第2个快速客户服务

3号普通窗口正在获取普通任务

3号普通窗口开始为第3号普通客户服务

4号普通窗口正在获取普通任务

4号普通窗口没有取到普通任务,先休息1秒钟

4号普通客户正在等待服务

5号快速窗口为第2个快速客户完成服务,服务时间1

5号快速窗口正在获取任务

5号快速窗口没有取到快速任务!

5号快速窗口正在获取普通任务

5号快速窗口没有取到普通任务,先休息1秒钟

4号普通窗口正在获取普通任务

4号普通窗口开始为第4号普通客户服务

5号普通客户正在等待服务

3号快速客户正在等待服务

6VIP窗口为第1VIP客户完成服务,服务时间3

6VIP窗口正在获取任务

6VIP窗口没有取到VIP任务!

6VIP窗口正在获取普通任务

6VIP窗口开始为第5号普通客户服务

5号快速窗口正在获取任务

5号快速窗口正在为第3个快速客户服务

6号普通客户正在等待服务

5号快速窗口为第3个快速客户完成服务,服务时间1

5号快速窗口正在获取任务

5号快速窗口没有取到快速任务!

5号快速窗口正在获取普通任务

5号快速窗口开始为第6号普通客户服务

7号普通客户正在等待服务

4号快速客户正在等待服务

2VIP客户正在等待服务

6VIP窗口为第5号普通客户完成服务,服务时间1

6VIP窗口正在获取任务

6VIP窗口正在为第2VIP客户服务

8号普通客户正在等待服务

1号普通窗口为第1号普通客户完成服务,服务时间6

1号普通窗口正在获取普通任务

1号普通窗口开始为第7号普通客户服务

5号快速客户正在等待服务

9号普通客户正在等待服务

5号快速窗口为第6号普通客户完成服务,服务时间2

5号快速窗口正在获取任务

5号快速窗口正在为第4个快速客户服务

10号普通客户正在等待服务

…...

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值