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

本文模拟了一个银行窗口调度系统,采用多线程技术实现了不同类型客户(VIP、快速、普通)在六个窗口(四个普通、一个快速、一个VIP)的排队叫号流程。系统通过线程池管理客户生成和窗口服务,利用号码管理器类协调不同业务类型的取号及服务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

---------------------- <a href="http://edu.youkuaiyun.com"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.youkuaiyun.com"target="blank">.Net培训</a>、期待与您交流! ----------------------

一、题目要求:

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

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

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

3.异步随机生成各种类型的客户,生成各类型用户的概率比例为:

VIP客户 :普通客户 :快速客户 =  1 :6 :3。

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

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

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

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

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

二、项目需求分析与设计

1.需求分析

根据项目需求,加上我们现实生活中的参照,模拟一下客户去银行办理业务的流程。首先,客户来到银行,先要去取号机器那里根据所要办理的业务类型获取相应的号码,而后在大厅排队等待服务。如果银行窗口叫号到自己时,就去相应的窗口办理业务。

项目中把银行业务分为3种类型:普通业务、快速业务和VIP业务,并且每种业务类型的处理时间不同。快速业务窗口和VIP业务窗口办理完业务后可以处理普通业务。

2.设计类

从项目需求中我们可以得到:客户业务类、服务窗口类、取号机器类等,细心分析,可以发现:取号机器管理3种类型的号码,每种类型的号码都有取号和被叫号的功能。所以可以再分出一个类为号码管理器类,管理本类所产生的号码。由于银行有3种业务支持办理,我们可以把客户业务类定义成一个枚举比较方便。每种业务办理所需的时间不同,项目需求中要求可以设置时间,那么可以把业务办理的时间也定义成一个类。

三、项目编程

 号码管理器类:NumberManager

具体编写步骤:

1.定义一个变量描述产生的号码的前一个号码,默认值为1。

2.定义一个集合存储取号机器打印的号码。

3.定义一个方法,用来描述取号机器不断的产生号码的功能。由于会被3种业务调用,所以要用synchronized进行同步。

4.定义银行窗口获得要服务的号码的功能,同上,也要进行同步。

package cn.isoftstone.interview.bank;

import java.util.ArrayList;
import java.util.List;

/**
 * 号码管理器类
 * @author 中关村阿旺
 *
 */
public class NumberManager {

	//产生号码的前一个号码,初始化值为1(也就是从号码1开始往后取号)
	private int lastNumber=1;
	//将正在取号排队等待服务的号码存放到一个集合中
	//因为号码是有 顺序的,先产生哪个号码,就应该先为哪个号码服务,所以在此选择ArrayList集合。
	private List<Integer> queueNumber =new ArrayList<Integer>();
	
	//产生新号码的方法
	public synchronized int generateNewNumber(){
		//将产生的号码添加到集合中
		queueNumber.add(lastNumber);
		//让前一个号码自增就是产生的新号码
		return lastNumber++;
	}
	
	//获得要服务的号码
	//因为queueNumber.remove()方法返回的是一个Integer对象,如果没有客户取号,就会返回null
	//所以,返回值定义成int不合适,有可能发生空指针异常,所以定义成int的包装类Integer
	public synchronized Integer fetchServiceNumber(){
		if(queueNumber.size()>0){
			//返回排行最小的那个号码
			return queueNumber.remove(0);
		}
		return null;
	}
}

取号机器类:NumberMachine

具体编写步骤:

1.由于银行支持3种业务类型,所以要创建3个号码管理器对象,模拟现实中一个取号机可以取出不同类型的号码。

每种号码管理器对象只可以在内存中存在1个,所以私有化这3个号码管理器对象的访问。

2.定义3个公共方法,分别获取这3个号码管理器对象。

3.私有化本类的构造方法,对外提供公共的获取本类实例的方法,确保内存中只有1个取号机器类。也就是说,现实生活中的取号机只能有1个。

package cn.isoftstone.interview.bank;

/**
 * 取号机器类
 * @author 中关村阿旺
 *
 */
public class NumberMachine {

	//创建3个号码管理器对象,分别处理普通窗口、快速窗口和VIP窗口的号码
	//普通窗口号码管理器
	private NumberManager commonManager =new NumberManager();
	//快速窗口号码管理器
	private NumberManager expressManager =new NumberManager();
	//VIP窗口号码管理器
	private NumberManager vipManager =new NumberManager();
	
	//创建3个方法,分别获得这3个号码管理器对象
	public NumberManager getCommonManager() {
		return commonManager;
	}
	public NumberManager getExpressManager() {
		return expressManager;
	}
	public NumberManager getVipManager() {
		return vipManager;
	}
	
	//要想让号码按照顺序取号和排队等待服务,所以每个号码管理器在内存中只能有1个对象,所以需要把本类定义成单例模式
	private NumberMachine(){
		
	}
	
	//对外提供一个公共静态方法获取本类对象
	private static NumberMachine instance =new NumberMachine();
	public static NumberMachine getInstance(){
		return instance;
	}
}

银行业务类型类:CustomerType

具体编写步骤:

1.根据项目需求可以得出此银行支持3种业务类型,所以把此类定义成一个枚举,并创建3个业务类型对象。

2.由于测试运行时,需要给出业务类型的中文名称,所以重写枚举的toString()方法。

package cn.isoftstone.interview.bank;

/**
 * 银行窗口办理的业务类型
 * @author 中关村阿旺
 *
 */
public enum CustomerType {

	//因为银行窗口只办理3类业务:普通、快速、VIP,所以优先使用枚举来定义
	COMMON,EXPRESS,VIP;
	
	//重写toString()方法
	public String toString(){
		switch(this){
		case COMMON:
			return "普通";
		case EXPRESS:
			return "快速";
		case VIP:
			return this.name();
		}
		//因为switch判断的是枚举类型,不可能有额外的值,为了让程序编译通过,返回null
		return null;
	}
}

 办理业务所耗费的时间的类:Constants

具体编写步骤:

1.自定义2个变量描述银行办理业务所要花费的最长时间和最短时间,为了让调用方便,访问修饰符设置为公共静态的。

2.定义1个变量描述默认的客户(普通客户)来银行办理业务的间隔时间,用public static修饰。

package cn.isoftstone.interview.bank;

/**
 * 办理业务所需时间的类
 * @author 中关村阿旺
 *
 */
public class Constants {

	//业务办理规定的最大时间
	public static int MAX_SERVICE_TIME=10000;
	//最小时间
	public static int MIN_SERVICE_TIME=1000;
	
	//普通客户来银行办理业务的间隔时间
	public static int COMMON_CUSTOMER_INTERVAL_TIME=1;
}

银行窗口类:ServiceWindow

具体编写步骤:

1.定义1个银行业务枚举对象,描述银行窗口办理的业务类型,默认为普通业务。

2.定义1个变量描述银行窗口的编号。

3.定义功能设置银行窗口办理的业务类型。因为需求中说明在没有快速业务或VIP业务的情况下,快速窗口和VIP窗口可以办理普通业务。

4.定义功能设置银行窗口的编号。

5.定义功能实现银行窗口办理业务(叫号)的方法,根据每个窗口的类型判断办理何种业务。

6.实现3种业务的办理方法,办理过程使用线程的sleep()方法进行模拟,办理完毕,给出办理时间。

当快速窗口或VIP窗口的业务处理完毕,可以处理普通业务。

package cn.isoftstone.interview.bank;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 银行服务窗口类
 * @author 中关村阿旺
 *
 */
public class ServiceWindow {
	//默认银行窗口办理业务的类型为普通业务
	private CustomerType type=CustomerType.COMMON;
	//办理业务的窗口编号,默认为1
	private int windowId=1;
	//设置窗口办理的业务类型
	public void setType(CustomerType type) {
		this.type = type;
	}
	//设置窗口的编号
	public void setWindowId(int windowId) {
		this.windowId = windowId;
	}

	//服务窗口开始叫号的方法
	public void start(){
		//产生一个线程池,在这个线程池中只存放一个线程
		ExecutorService pool=Executors.newSingleThreadExecutor();
		//使用这个线程池让服务窗口叫号
		pool.execute(new Runnable() {
			public void run() {
				while(true){
					switch(type){
					case COMMON:
						commonService();
						break;
					case EXPRESS:
						expressService();
						break;
					case VIP:
						vipService();
						break;
					}
				}
			}
		});
	}
	
	//VIP窗口办理业务的方法
	private void vipService() {
		String windowName="第"+windowId+"号"+type+"窗口";
		//调用获得办理服务的号码的方法
		Integer number=NumberMachine.getInstance().getVipManager().fetchServiceNumber();
		System.out.println(windowName+"正在获取VIP任务。");
		if(number != null){
			System.out.println(windowName+"为第"+number+"号"+type+"客户服务。");
			//业务办理开始的时间
			long beginTime=System.currentTimeMillis();
			int maxRandomTime=Constants.MAX_SERVICE_TIME-Constants.MIN_SERVICE_TIME;
			//业务办理所需的时间(产生的随机数位于1001~10000之间)
			long serverTime=new Random().nextInt(maxRandomTime)+1+Constants.MIN_SERVICE_TIME;
			try {
				//让线程休眠,表示正在办理业务
				Thread.sleep(serverTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//结束的时间
			long endTime=System.currentTimeMillis();
			System.out.println(windowName+"为第"+number+"号"+type+"客户服务完毕,耗时"+((endTime-beginTime)/1000)+"秒。");
		}else{
			System.out.println(windowName+"没有获取到任务。");
			//没有VIP业务,那么会处理普通业务
			commonService();
		}
	}
	
	//快速窗口办理业务的方法
	private void expressService() {
		String windowName="第"+windowId+"号"+type+"窗口";
		Integer number=NumberMachine.getInstance().getExpressManager().fetchServiceNumber();
		System.out.println(windowName+"正在获取快速任务。");
		if(number != null){
			System.out.println(windowName+"为第"+number+"号"+type+"客户服务。");
			//业务办理开始的时间
			long beginTime=System.currentTimeMillis();
			//需求中说明快速客户办理业务所需的时间为最小值
			long serverTime=Constants.MIN_SERVICE_TIME;
			try {
				Thread.sleep(serverTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//结束的时间
			long endTime=System.currentTimeMillis();
			System.out.println(windowName+"为第"+number+"号"+type+"客户服务完毕,耗时"+((endTime-beginTime)/1000)+"秒。");
		}else{
			System.out.println(windowName+"没有获取到任务。");
			//没有办理快速业务的客户,那么会处理普通客户的业务
			commonService();
		}
	}

	//普通窗口办理业务的方法
	private void commonService() {
		String windowName="第"+windowId+"号"+type+"窗口";
		Integer number=NumberMachine.getInstance().getCommonManager().fetchServiceNumber();
		System.out.println(windowName+"正在获取普通任务。");
		if(number != null){
			System.out.println(windowName+"为第"+number+"号普通客户服务。");
			//业务办理开始的时间
			long beginTime=System.currentTimeMillis();
			int maxRandomTime=Constants.MAX_SERVICE_TIME-Constants.MIN_SERVICE_TIME;
			// int nextInt(int n) 
		    //返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的 int 值。 
			//业务办理所需的时间(产生的随机数位于1001~10000之间)
			long serverTime=new Random().nextInt(maxRandomTime)+1+Constants.MIN_SERVICE_TIME;
			try {
				Thread.sleep(serverTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//结束的时间
			long endTime=System.currentTimeMillis();
			System.out.println(windowName+"为第"+number+"号普通客户服务完毕,耗时"+((endTime-beginTime)/1000)+"秒。");
		}else{
			System.out.println(windowName+"没有获取到任务,空闲1秒。");
			try {
				//没有普通客户的业务,线程休眠1秒后再继续获取任务
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 银行业务调度系统的测试类:MainClass

具体编写步骤:

1.首先根据需求定义,创建4个普通窗口对象、1个快速窗口对象和1个VIP窗口对象,并分别设置窗口编号、类型,设置完毕后,开始办理服务。

2.使用Executors类的newScheduledThreadPool()方法创建3个线程池,每个线程池中只存放1个线程。

每隔一段时间,使此3个线程各创建1个客户对象。根据需求,客户产生的时间间隔不同。

package cn.isoftstone.interview.bank;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 银行业务调度系统测试类
 * 
 * @author 中关村阿旺
 * 
 */
public class MainClass {

	public static void main(String[] args) {
		// 根据项目需求,创建4个普通窗口对象
		for (int i = 1; i < 5; i++) {
			ServiceWindow commonWindow = new ServiceWindow();
			// 由于默认窗口类型是普通窗口,所以不必设置窗口类型
			// 设置每个普通窗口的编号
			commonWindow.setWindowId(i);
			// 开始办理服务(叫号)
			commonWindow.start();
		}

		// 创建1个快速窗口对象
		ServiceWindow expressWindow = new ServiceWindow();
		// 设置快速窗口的编号
		expressWindow.setWindowId(5);
		// 设置快速窗口的类型
		expressWindow.setType(CustomerType.EXPRESS);
		// 开始办理服务(叫号)
		expressWindow.start();

		// 创建1个VIP窗口对象
		ServiceWindow vipWindow = new ServiceWindow();
		// 设置VIP窗口的编号
		vipWindow.setWindowId(6);
		// 设置VIP窗口的类型
		vipWindow.setType(CustomerType.VIP);
		// 开始办理服务(叫号)
		vipWindow.start();

		// 创建一个线程池,线程池中有1个线程可以被调用。
		ScheduledExecutorService pool1 = Executors.newScheduledThreadPool(1);
		// 设置每隔多长时间调用一次指定的代码(产生普通客户)
		pool1.scheduleAtFixedRate(new Runnable() {
			public void run() {
				//得到普通客户所取出的号码
				int number=NumberMachine.getInstance().getCommonManager().generateNewNumber();
				System.out.println("第"+number+"号普通客户正在等待服务!");
			}
				}, 
				0, // 此方法中的代码多长时间后会被线程调用
				Constants.COMMON_CUSTOMER_INTERVAL_TIME, // 以后此方法中的代码每隔多长时间会被线程调用
				TimeUnit.SECONDS); // 时间单位

		// 创建一个线程池,线程池中有1个线程可以被调用。
		ScheduledExecutorService pool2 = Executors.newScheduledThreadPool(1);
		// 设置每隔多长时间调用一次指定的代码(产生快速客户)
		pool2.scheduleAtFixedRate(new Runnable() {
			public void run() {
				//得到快速客户所取出的号码
				int number=NumberMachine.getInstance().getExpressManager().generateNewNumber();
				System.out.println("第"+number+"号快速客户正在等待服务!");
			}
				}, 
				0, // 此方法中的代码多长时间后会被线程调用
				Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2, // 以后此方法中的代码每隔多长时间会被线程调用
				TimeUnit.SECONDS); // 时间单位

		// 创建一个线程池,线程池中有1个线程可以被调用。
		ScheduledExecutorService pool3 = Executors.newScheduledThreadPool(1);
		// 设置每隔多长时间调用一次指定的代码(产生VIP客户)
		pool3.scheduleAtFixedRate(new Runnable() {
			public void run() {
				//得到VIP客户所取出的号码
				int number=NumberMachine.getInstance().getVipManager().generateNewNumber();
				System.out.println("第"+number+"号VIP客户正在等待服务!");
			}
				}, 
				0, // 此方法中的代码多长时间后会被线程调用
				Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6, // 以后此方法中的代码每隔多长时间会被线程调用
				TimeUnit.SECONDS); // 时间单位
	}
}


---------------------- <a href="http://edu.youkuaiyun.com"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.youkuaiyun.com"target="blank">.Net培训</a>、期待与您交流! ----------------------

详细请查看:<a href="http://edu.youkuaiyun.com" target="blank">http://edu.youkuaiyun.com</a>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值