(黑马程序员)学习笔记,银行业务调度系统

银行业务调度系统
需求:
模拟实现银行业务调度系统逻辑,具体需求如下:
* 银行内有6个业务窗口,1-4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口
* 有3种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类的业务)。
* 伊布随机生成各种类型的客户,生成各类型用户比例为:
VIP客户:普通客户:快速客户 = 1 : 6 : 3
* 客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需时间,
 快速客户办理业务所需时间为最小值(提示;办理业务的过程可通过线程的sleep方式模拟)
* 各类型客户在其对应的窗口按顺序依次办理业务
* 当VIP窗口(6号窗口)和快速窗口(5号窗口)没有客户等待办理业务时,两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理,则优先处理对应客户的业务
* 随机生成客户时间间隔,业务办理时间最大值和最小值自定,可以设置
* 不要求实现GUI,只考虑系统逻辑实现,可通过log方式展现程序运行结果

分析:
日常去银行办理业务可以发现客户到银行办理事务时,先要去取号机领取一个服务号码,然后等待服务窗口叫号,而且不同的窗口会处理不同的事务。
1.分析取号机
取号机在银行中一般只有1个,但是可以取出不同类型的服务号码(VIP、快速、普通)。
* 取号机需要用单例模式定义,因为只有1个
* 取号机可以取3种不同类型的服务号码,取号机提供get()方法,进行取号。

2.分析取号过程
取号机先要产生一个号码,取出号码要加入等待队列中,然后等待窗口叫号。
* 需要一个可变数组来存放被取出的号码(即等待队列),号码机产生的号码每取一次要加1。
* 取号机产生号码应该有个初始值,这里从1开始,然后客户通过一个方法获得号码并添加到等待队列中。
* 窗口叫号,会从等待序列中的获得一个最早号码,并把这个号码从队列中移除

3.分析服务窗口
每个窗口都会处理对应自己类型的服务,而且同一类型的窗口可能会有多个,因此需要窗口编号。每个窗口处理事务的时间也不一样,因此需要多线程来操作所有窗口。窗口服务时有时间规定的,应该有个最大时间和最小时间,工作的时间就应该在这个时间范围内。
* 通过set()方法来设定窗口号和窗口类型,默认窗口号从1开始,类型是普通窗口
* 使用线程池,根据类型判断需要开启哪种类型的窗口,来开启多个窗口服务线程
* 每次窗口处理事务需要根据规定的最大最小时间计算服务时间
* 3种类型的窗口除了普通窗口外,其他的窗口在没有对应客户等待时,处理普通类型服务,这里可以复用普通窗口的代码

4.固定的类型
固定3种类型,因此使用枚举
* 3种类型分别是普通、快速、VIP。向外提供toString()方法,显示对应的汉字


5.用到的常量
对于常量,应该使用一个单独的类来保存
* 这里用到了最大服务时间、最小服务时间、普通客户取号间隔时间、快速客户取号间隔时间和VIP客户取号间隔时间


6.运行测试
先开启各个窗口,按着需求开启4个普通窗口,1个快速窗口,1个VIP窗口。然后使用定时器控制客户取号,根据客户比例控制取号间隔时间。

代码:
1.取号机
package com.interview.bank;
/*
 * 取号机,3种号码类型
 * 1个取号机,提供3种不同类型的号码
 */
public class NumberMachine {

	//普通号码
	private NumberManager commonManager = new NumberManager();
	//VIP号码
	private NumberManager VIPManager = new NumberManager();
	//快速号码
	private NumberManager expressManager = new NumberManager();
	
	//为3种号码提供获取方法
	public NumberManager getCommonManager() {
		return commonManager;
	}
	public NumberManager getVIPManager() {
		return VIPManager;
	}
	public NumberManager getExpressManager() {
		return expressManager;
	}
	
	//号码种类不同,但是取号机就一个,单例模式
	private NumberMachine(){}
	private static NumberMachine numberMachine = null;
	public static NumberMachine newInstance(){
		if(numberMachine==null){
			synchronized(NumberMachine.class){
				if(numberMachine==null){
					numberMachine = new NumberMachine();
				}
			}
		}
		return numberMachine;
	}
}
2.取号过程
package com.interview.bank;
/*
 * 取号过程管理
 * 客户取号,从1开始取服务等待号码
 */
import java.util.ArrayList;
import java.util.List;

public class NumberManager {

	//取号机依次发放的号码,号码从1开始
	private int lastNumber = 1;
	//用来存放被取的号码队列(即等待服务的号码队列),这里使用集合便于动态加载
	private List<Integer> serviceNumbers = new ArrayList<Integer>();
/*
 * 小细节:
 * 1. 客户取号和窗口处理都要同步,不然就会出现多个人取到同一个号的现象
 *    这里使用了synchronized关键字保持同步
 * 2. 要注意的是号码队列一开始是空的 ,如果在没有存数据的时候就被人调用remove()方法,就会报空指针异常
 *    这里将返回值int 改成Integer,
 *    初始化的lastNumber虽然是int,不过java可以自动装箱和拆箱
 */

	//客户取服务号,并将号码添加到待服务号码队列中
	public synchronized Integer generateNewManager(){
		serviceNumbers.add(lastNumber);
		return lastNumber++;
	}
	//处理服务号,按顺序为处于等待的号码进行服务,服务后该号码作废
	public synchronized Integer fetchServiceNumber(){
		Integer num = null;
		if(serviceNumbers!=null&&serviceNumbers.size()>0){
			return serviceNumbers.remove(0);
		}
		return num;
	}
	
}

3.服务窗口
package com.interview.bank;
/*
 * 服务窗口:
 * 模拟银行职员在窗口叫号,然后处理事务的情景
 */
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServiceWindow {

	//窗口号,从1号开始
	private int windowID = 1;
	//服务窗口类型,默认是普通窗口
	private CustomerType type = CustomerType.COMMON;
	
	/*
	 * set()方法,用于设置窗口号和窗口类型
	 */
	//设置窗口号
	public void setWindowID(int windowID){
		this.windowID = windowID;
	}
	//设置窗口类型
	public void setType(CustomerType type){
		this.type = type;
	}
	//进行窗口取号,不同类型的窗口,服务的客户类型也不同,因为每个窗口处理的时间不同,这里使用多线程执行窗口服务
	public void start(){
		ExecutorService esPool = Executors.newSingleThreadExecutor();
		esPool.execute(new Runnable(){
			@Override
			public void run() {
				while(true){
					//这里不是if else ,是因为使用switch效率要高
					//根据不同的类型进行服务
					switch(type){
						case COMMON:
							commonWindow();
							break;
						case EXPRESS: 
							expressWindow();
							break;
						case VIP:
							vipWindow();
							break;
					}
				}
			}
		});
	}
	
	//普通服务窗口
	private void commonWindow() {
		//获得窗口名称
		String windowName = "第"+windowID+"号"+type+"窗口";
		//获得普通客户等待号码
		Integer number = NumberMachine.newInstance().getCommonManager().fetchServiceNumber();
		System.out.println(windowName+"开始取号");
		
		if(number!=null){
			System.out.println(windowName+"正在为第"+number+"个"+"普通客户服务");
			//服务时间,开始计时
			long begin = System.currentTimeMillis();
			//服务时间应该在规定的最大最小服务时间范围中
			int maxRandomValue = Constants.MAX_SERVICETIME-Constants.MIN_SERVICETIME;
			long serviceTime = new Random().nextInt(maxRandomValue)+1+Constants.MIN_SERVICETIME;
			try {
				//系统等待,模拟服务所消耗的时间
				Thread.sleep(serviceTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long end = System.currentTimeMillis();
			System.out.println(windowName+"为第"+number+"个"+"普通客户完成服务,耗时"+(end-begin)/1000+"秒");
		}else{
			//如果没有客户等待服务,等待1秒
			System.out.println(windowName+"没有取到号码,休息一下,1秒后再次取号");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	//快速服务窗口
	private void expressWindow() {
		//获得窗口名称
		String windowName = "第"+windowID+"号"+type+"窗口";
		//获得快速客户等待号码
		Integer number = NumberMachine.newInstance().getExpressManager().fetchServiceNumber();
		System.out.println(windowName+"开始取号");
		if(number!=null){
			System.out.println(windowName+"正在为第"+number+"个"+type+"客户服务");
			//服务时间,开始计时
			long begin = System.currentTimeMillis();
			//快速窗口在处理快速服务时,总是用最短的时间
			try {
				Thread.sleep(Constants.MIN_SERVICETIME);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long end = System.currentTimeMillis();
			System.out.println(windowName+"为第"+number+"个"+type+"客户完成服务,耗时"+(end-begin)/1000+"秒");
		}else{
			//如果没有快速客户在等待,则获取普通客户等待号码,处理普通服务
			System.out.println(windowName+"没有取到号码");
			commonWindow();
		}
	}
	
	//VIP服务窗口
	private void vipWindow() {
		//获得窗口名称
		String windowName = "第"+windowID+"号"+type+"窗口";
		//获得VIP客户等待号码
		Integer number = NumberMachine.newInstance().getVIPManager().fetchServiceNumber();
		System.out.println(windowName+"开始取号");
		if(number!=null){
			System.out.println(windowName+"正在为第"+number+"个"+type+"客户服务");
			//服务时间,开始计时
			long begin = System.currentTimeMillis();
			//VIP窗口这是优先处理VIP客户,而服务时间应该在规定的最大最小服务时间范围中
			int maxRandomValue = Constants.MAX_SERVICETIME-Constants.MIN_SERVICETIME;
			long serviceTime = new Random().nextInt(maxRandomValue)+1+Constants.MIN_SERVICETIME;
			try {
				Thread.sleep(serviceTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long end = System.currentTimeMillis();
			System.out.println(windowName+"为第"+number+"个"+type+"客户完成服务,耗时"+(end-begin)/1000+"秒");
		}else{
			//如果没有VIP客户在等待,则获取普通客户等待号码,处理普通服务
			System.out.println(windowName+"没有取到号码");
			commonWindow();
		}
	}
}

4.类型枚举类
package com.interview.bank;
/*
 * 类型枚举:
 * 一共3种类型,分别为普通(COMMON),快速(EXPRESS),VIP
 */
public enum CustomerType {

	//分别为普通类型,快速类型,VIP类型
	COMMON,VIP,EXPRESS;
	//设定类型输出文字
	public String toString(){
		switch(this){
			case COMMON:
				return "普通";
			case EXPRESS:
				return "快速";
			case VIP:
				return "VIP";
		}
		return null;
	}
}

5.常量类
package com.interview.bank;
/*
 * 常量:
 * 保存用到的所有常量,包括最大和最小服务时间,各种类型客户的取号间隔时间(根据间隔时间来划分各类型客户的比例)
 */
public class Constants {

	//最大服务时间
	public static int MAX_SERVICETIME = 10000;
	//最小服务时间
	public static int MIN_SERVICETIME = 1000;
	//普通客户取号间隔时间
	public static int COMMON_COSTOMER_TIME = 1;
	//快速客户取号间隔时间
	public static int EXPRESS_COSTOMER_TIME = 3;
	//VIP客户取号间隔时间
	public static int VIP_COSTOMER_TIME = 6;
}

6.运行测试

package com.interview.bank;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/*
 * 运行测试:
 * 创建服务窗口:4个普通的,1个快速的,1个VIP的
 * 使用调度线程模拟各种客户取号
 */
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个VIP窗口
		ServiceWindow vipWindow = new ServiceWindow();
		//设置窗口类型为VIP
		vipWindow.setType(CustomerType.VIP);
		//开启窗口服务线程
		vipWindow.start();
		
		//创建1个快速窗口
		ServiceWindow expressWindow = new ServiceWindow();
		//设置窗口类型为快速
		expressWindow.setType(CustomerType.EXPRESS);
		//开启窗口服务线程
		expressWindow.start();
		
		// 使用定时器为不同类型的客户取号,通过设置时间间隔控制客户类型比例
		//普通客户取号,每隔1秒取1次
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){
					@Override
					public void run() {
						//普通客户取号等待
						Integer number = NumberMachine.newInstance().getCommonManager().generateNewManager();
						System.out.println(number+"号普通客户等待服务");
					}
				}, 
				0, 
				Constants.COMMON_COSTOMER_TIME, 
				TimeUnit.SECONDS);
		//快速客户取号,每隔3秒取1次
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){
					@Override
					public void run() {
						//普通客户取号等待
						Integer number = NumberMachine.newInstance().getExpressManager().generateNewManager();
						System.out.println(number+"号快速客户等待服务");
					}
				}, 
				0, 
				Constants.EXPRESS_COSTOMER_TIME, 
				TimeUnit.SECONDS);
		//VIP客户取号,每隔6秒取1次
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){
					@Override
					public void run() {
						//普通客户取号等待
						Integer number = NumberMachine.newInstance().getVIPManager().generateNewManager();
						System.out.println(number+"号VIP客户等待服务");
					}
				}, 
				0, 
				Constants.VIP_COSTOMER_TIME, 
				TimeUnit.SECONDS);
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值