黑马程序员——7K面试题: 银行业务调度系统

黑马程序员——7K面试题:银行业务调度系统

-------android培训java培训、期待与您交流!---------- 

(银行业务调度系统)面试题: 

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

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

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

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

       VIP客户:普通客户 :快速客户  =  163

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

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

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

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

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

 

——————————————题目解析——————————————

源文件解析:   (排序为建议创建代码顺序)

试题代码及解析详见:

 http://dl.vmall.com/c0y38zarjt?v=497798923 Heima(7K面试题)bankqueue包源文件。

1NumberMachine : 取号机: 提供客户取号(号码数,并存储到号码集合)取号集合系统中取号处理(删除)两功能方法;

2NumberManager : 窗口提取不同类型客户号码类;

3ServiceWindow : 银行窗口系统(不同窗口处理客户任务)

3.1CustomerType : 常量表,存储客户类型常量名;  【根据需要时创建】

3.2Constants : 常量表,用于存储窗口服务客户所需时间常量(配置文件)

4MainClass 主函数类,创建银行窗口与取号系统;

 

一、取号机:   (NumberManager号码集合类NumberMachine取号系统)

   1、根据用户比例按不同时间间隔生成不同用户类型号码id

   2、当银行窗口Window来访问取号机时,查看对应的用户群集合中是否有用户id

   (有则返回该id号给该窗口操作,在集合中删除该id号)

代码示例:

1、NumberManager号码集合类(号码存储):

package com.itheima.bankqueue;
import java.util.ArrayList;
import java.util.List;

public class NumberManager {
	private int lastNumber = 0;
	private List<Integer> queueNumbers = new ArrayList<Integer>();
	
	//取号机的取号方法,返回号码数。同时将该数存到取号系统(集合)中;
	public synchronized Integer generateNewNumber(){
		queueNumbers.add(++lastNumber);
		return lastNumber;
	}
	
	//从取号集合系统中取号处理,为银行窗口提供的方法;
	public synchronized Integer fetchNumber(){
		if(queueNumbers.size()>0){
			//返回取得的号码数,同时在号码系统(集合)中删除该号码元素;
			return (Integer)queueNumbers.remove(0); 
		}else{
			return null;
		}
	}
}

2、NumberMachine取号系统: (唯一,建议设计成单例)

package com.itheima.bankqueue;

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;
	}
	public NumberManager getVipManager(){
		return vipManager;
	}
}

二、银行窗口(ServiceWindow与配置:CustomerTypeConstants):

   1、有三种类型窗口: 普通(Service)、快速(express)VIP

   2、处理不同类型用户,耗时不同;且普通和VIP类型用户耗时随机(最大与最小间)

   3、普通窗口为基础,当快速、VIP没有客户时需要查看是否有普通客户需要帮助处理;

   4、不同窗口向取号机取号,之后处理客户业务,为便于记录,打印开始准备(访问取号机)、在处理(返回)id中、完成后打印耗时;

【建议:

   1、将时间常量封装为单独类(或枚举)的成员变量,然后对其调用,因为如果要完整,可通过反射读取配置文件,方便后期修改处理业务时间;

   2、根据情况,看是否将快速、VIP的处理定义为普通的子类/ 方法;(因为传入类型不同、个别处理方式不同),且快速与VIP是要帮助处理普通客户,因此也要在处理方式中有调用处理普通客户的方法;           

3、银行窗口ServiceWindow: 

package com.itheima.bankqueue;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

/**
 * 将VIP窗口和快速窗口定义为平行方法,而不做成子类,
 * 是因为实际业务中的普通窗口可以随时可设置为VIP窗口和快速窗口。
 * */
public class ServiceWindow {
	
	//指定日志文件存储位置;
	private static Logger logger = Logger.getLogger("cn.itcast.bankqueue");
	
	private CustomerType type = CustomerType.COMMON;
	private int number = 1;
	
	public CustomerType getType() {
		return type;
	}
	public void setType(CustomerType type) {
		this.type = type;
	}
	public void setNumber(int number) {
		this.number = number;
	}
	
	public void start(){
		Executors.newSingleThreadExecutor().execute(
				new Runnable(){
					@Override
					public void run() {
					//最好是把while放在case下面,比while()包围switch代码效率高;
					//因为此为单线程执行代码,指定为某种类型窗口后将不断执行对应类型的客户任务,无需重复判断;
						switch(type){
						case COMMON: 
							while(true)commonService();
							//break;
							case EXPRESS:
							while(true)expressService();
							//break;
							case VIP:
							while(true)vipService();
							//break;
							}
						
					}
				});
	}
	
	//创建普通窗口获取任务方法;
	private void commonService() {
		//打印正在获取任务语句;
		String windowName = "第"+ number + "号"+type+"窗口";
		System.out.println(windowName+"开始获取普通任务");
		
		//从普通取号集合中提取号码任务;
		Integer serviceNumber = 
			NumberMachine.getInstance().getCommonManager().fetchNumber();
		//判断是否获得任务,如果有则为该客户服务;
		if(serviceNumber!=null){
			System.out.println(windowName+"开始为第"+serviceNumber+"号普通客户服务");
			//服务普通客户所需时间(随机性);
			int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
			int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;

			try {
				//处理任务中,线程进入睡眠;
				Thread.sleep(serviceTime); 
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//窗口完成服务客户任务,提示并打印服务时间;
			System.out.println(windowName + "完成为第" + serviceNumber + "号普通客户服务,总共耗时" + serviceTime/1000 + "秒");
		}else{
			//如果没获取到任务,打印此提示语句;
			System.out.println(windowName + "没有取到普通任务,正在空闲一秒"); 
			try {
				Thread.sleep(1000);      //空闲休息中,有点小多余,不过仿真现实情况;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
	}
	private void expressService() {
		//打印正在获取任务语句;
		String windowName = "第"+ number + "号"+type+"窗口";
		System.out.println(windowName+"开始获取快速任务");
		//从快速取号集合中提取号码任务;
		Integer serviceNumber = 
				NumberMachine.getInstance().getExpressManager().fetchNumber();
		
		//判断是否获得任务,如果有则为该客户服务;
		if(serviceNumber!=null){
			System.out.println(windowName+"开始为第"+serviceNumber+"号快速客户服务");
			//处理快速客户任务只需最短任务时间;
			int serviceTime = Constants.MIN_SERVICE_TIME;
			try {
				//处理任务中,线程进入睡眠;
				Thread.sleep(serviceTime); 
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//窗口完成服务客户任务,提示并打印服务时间;
			System.out.println(windowName + "完成为第" + serviceNumber + "号快速客户服务,总共耗时" + serviceTime/1000 + "秒");
		}else{
			//如果没获取到任务,打印此提示语句;
			System.out.println(windowName + "没有取到快速任务,正在空闲一秒"); 
			try {
				Thread.sleep(1000);      //空闲休息中,有点小多余,不过仿真现实情况;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//如果快速窗口没有快速客户任务,则帮忙处理普通客户任务;
			commonService();
		}
			
		
	}
	private void vipService() {
		//打印正在获取任务语句;
		String windowName = "第"+ number + "号"+type+"窗口";
		System.out.println(windowName+"开始获取VIP任务");
		//从快速取号集合中提取号码任务;
		Integer serviceNumber = 
				NumberMachine.getInstance().getVipManager().fetchNumber();
		
		//判断是否获得任务,如果有则为该客户服务;
		if(serviceNumber!=null){
			System.out.println(windowName+"开始为第"+serviceNumber+"号VIP客户服务");
			//服务普通客户所需时间(随机性);
			int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
			int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;

			try {
				//处理任务中,线程进入睡眠;
				Thread.sleep(serviceTime); 
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//窗口完成服务客户任务,提示并打印服务时间;
			System.out.println(windowName + "完成为第" + serviceNumber + "号VIP客户服务,总共耗时" + serviceTime/1000 + "秒");
		}else{
			//如果没获取到任务,打印此提示语句;
			System.out.println(windowName + "没有取到VIP任务,正在空闲一秒"); 
			try {
				Thread.sleep(1000);      //空闲休息中,有点小多余,不过仿真现实情况;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//如果快速窗口没有快速客户任务,则帮忙处理普通客户任务;
			commonService();
		}
	}
}

4、配置常量表CustomerTypeConstants (Java框架程序中建议将程序调用常量属性分类单独存储成一个类);

package com.itheima.bankqueue;

public enum CustomerType {
	COMMON,EXPRESS,VIP;
	public String toString(){
		String name = null;
		switch(this){
		case COMMON: 
			name = "普通";
			break;
		case EXPRESS: 
			name = "快速";
			break;
		case VIP: 
			name = name();
			break;
		}
		return name;
	}
}

package com.itheima.bankqueue;

public class Constants {
	//银行窗口处理客户任务所需的最长与最短时间;
	public static int MAX_SERVICE_TIME = 10000; //10秒;
	public static int MIN_SERVICE_TIME = 1000;  //1秒;
	
	//产生一个普通客户所需时间;
	public static int COMMON_CUSTOMER_INTERVAL_TIME = 1;
	/*解析:  每个普通窗口服务一个客户的平均时间为5秒,一共有4个这样的窗口,
	 * 也就是说银行的所有普通窗口合起来,平均1.25秒内可以服务完一个普通客户,
	 * 再加上快速窗口和VIP窗口也可以服务普通客户,所以 1秒钟产生一个普通客户比较合理。*/
}

三、主程序类MainClass:

   1、创建不同个数的不同银行类型窗口(线程)

   2、创建客户取号线程池,以定时器方式来按不同时间间隔生成用户id号。

package com.itheima.bankqueue;
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<5;i++){
			ServiceWindow window =  new ServiceWindow();
			window.setNumber(i);   //以数字编号命名普通窗口名;
			window.start();		   //启动普通窗口线程;
		}
	
		//产生1个快速窗口
		ServiceWindow expressWindow =  new ServiceWindow();
		expressWindow.setType(CustomerType.EXPRESS);  //以枚举值命名窗口;
		expressWindow.start();
		
		//产生1个VIP窗口		
		ServiceWindow vipWindow =  new ServiceWindow();
		vipWindow.setType(CustomerType.VIP);		//以枚举值命名窗口;
		vipWindow.start();		
		
		//普通客户从取号机拿号: 单线程定时器,每间隔指定时间就产生一名普通客户(间隔时间可由Constants文件配置);
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
			new Runnable(){
				public void run(){
					Integer serviceNumber = NumberMachine.getInstance().getCommonManager().generateNewNumber();
					/**面试题要求以log方式打印显示信息,但是
					 * 采用logger方式,无法看到直观的运行效果,因为logger.log方法内部并不是直接把内容打印出出来,
					 * 而是交给内部的一个线程去处理,所以,打印出来的结果在时间顺序上看起来很混乱。
					 */
					//logger.info("第" + serviceNumber + "号普通客户正在等待服务!");
					//普通客户成功从取号机拿到号并未普通取号集合添加数据: 
					System.out.println("第" + serviceNumber + "号普通客户正在等待服务!");						
				}
			},
			0,
			Constants.COMMON_CUSTOMER_INTERVAL_TIME, 
			TimeUnit.SECONDS);
		
		//快速客户拿号: 同上,间隔时间为产生普通客户的两倍,即快速客户数为普通客户的一半数量(普通:快速 = 6:3);
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
			new Runnable(){
				public void run(){
					Integer serviceNumber = NumberMachine.getInstance().getExpressManager().generateNewNumber();
					System.out.println("第" + serviceNumber + "号快速客户正在等待服务!");					}
				},
			0,
			Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2, 
			TimeUnit.SECONDS);
		
		//VIP客户拿号: 同上,间隔时间为产生普通客户的六倍,即快速客户数为普通客户数量的六分之一(普通:VIP = 6:1);
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
			new Runnable(){
				public void run(){
					Integer serviceNumber = NumberMachine.getInstance().getVipManager().generateNewNumber();
					System.out.println("第" + serviceNumber + "号VIP客户正在等待服务!");
				}
			},
			0,
			Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6, 
			TimeUnit.SECONDS);
	}
}

PS: 

1、是窗口获取任务时仿真现实等待,否则可以将线程执行创建银行窗口代码单独封装,从而可使用线程池来实现普通窗口共享数据多线程操作

Executors.newFixedThreadPool(intnThreadsThreadFactory threadFactory) 

:指定线程数去共同操作共享数据线程任务

2、提升或建议: 在教程及此源代码中创建三种类型多线程窗口时创建了三个ServiceWindow银行窗口系统,也导致无法将快速与VIP窗口确立为56号窗口;

改进: 可在第1点的基础上引入线程局部变量ThreadLocal,使不同类型窗口既能拥有独立的窗口名,也能采用共享的窗口排序号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值