银行出纳员仿真
问题描述:银行会有很多来办业务的顾客,他们会排队等待服务;对于银行方面他们派出出纳员来服务顾客,如果排队的顾客数量过多,银行就会增加出纳员的数量,如果顾客的数目过少,则减少出纳员的数目;总之要保持一个平衡。
仿真思路:
封装Customer类来表示顾客,每个顾客对象都会有一个需要服务的时间;使用有限容量的阻塞队列CustomerLine来模拟顾客的排队队列。
封装CustomerGenerator类来产生顾客,然后将产生的顾客加入到CustomerLine中去。
封装Teller类来表示银行的出纳员,Teller会从CustomerLine中取出Customer来进行服务。
封装TellerManage来管理所有的Teller及根据顾客/出纳员的比例来调整服务顾客的Teller数量。
在这里我们通过阻塞队列CustomerLine实现了Teller线程和CustomerGenerator线程之间的通信。
具体的实现代码如下:
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 多线程模拟银行出纳员问题
* 程序中只有一个顾客等待队列CustomerLine的实例对象;
* CustomerGenerator操纵CustomerLine的对象引用向顾客等待队列中增加顾客;
* TellerManager在构造器中初始化了一个出纳员服务实例进行服务,执行run()时:
* 在等待指定的调度时间之后,通过对顾客等待队列与当前出纳员服务队列的长度关系进行判断,
* 做出增加/不变/减少出纳员的操作抉择.且在顾客等待队列为空的情况下,要求出纳员服务队列至少存在一个出纳员
*/
//模拟顾客类,完全只是一个可读类,不需要同步
class Customer {
//该顾客所需服务时间
private final int serviceTime;
public Customer(final int serviceTime) {
this.serviceTime = serviceTime;
}
public int getServiceTime() {
return serviceTime;
}
public String toString() {
return "[" + serviceTime + "]";
}
}
//模拟顾客排队的队列,继承了阻塞队列,是一个有最大长度的队列
//是一个多线程共享对象,这个队列继承的是ArrayBlocingQueue
//阻塞添加:当队列元素已满时,队列会阻塞加入元素的线程,直到队列不满时才重新唤醒线程执行元素加入操作。
//阻塞删除:在队列元素为空时,队列会阻塞删除元素的线程,直到队列不空时才重新唤醒线程执行元素删除操作。
class CustomerLine extends ArrayBlockingQueue<Customer> {
//指定允许队列的最大长度
public CustomerLine(int maxSize) {
super(maxSize);
}
//重写toString()方法,用来进行显示当前排队中的顾客
public String toString() {
if (this.size() == 0)
return "[Empty]";
StringBuilder result = new StringBuilder();
for (Customer customer : this) {
result.append(customer);
}
return result.toString();
}
}
//顾客生产类
//间隔随机然后向队列中添加一位顾客的线程
class CustomerGenerator implements Runnable {
private CustomerLine customerLine; //顾客等待队列
private static Random rand = new Random(47);
public CustomerGenerator(CustomerLine customerLine) {
this.customerLine = customerLine;
}
public void run() {
try {
while (!Thread.interrupted()) {
//线程睡眠随机时间以后,产生一个顾客对象,添加到队列中
TimeUnit.MILLISECONDS.sleep(rand.nextInt(300));
//添加一个服务时间随机的顾客
customerLine.add(new Customer(rand.nextInt(1000)));
}
} catch (InterruptedException ex) {
System.out.println(this + " 通过中断异常退出");
}
System.out.println(this + " terminating");
}
}
//出纳员类,负责对队列中的顾客进行服务
//注意其有两种状态:服务顾客或做一些其它的事情
class Teller implements Runnable, Comparable<Teller> {
private static int counter = 0;
private final int id = counter++;//为出纳员对象实例设置对应的区分标志
//该Teller服务的顾客队列
private CustomerLine customerLine;
private int customerServed = 0;//已服务的顾客数
//标志目前是被分配到服务CustomerLine还是做一些其它事
//默认是分配给customerLine
private boolean servingCustomerLine = true;
public Teller(CustomerLine cl) {
this.customerLine = cl;
}
//正常情况下会从CustomerLine中取出一个Customer进行服务
//如果被分配到做其它事,则会被挂起
public void run() {
try {
while (!Thread.interrupted()) {
Customer customer = customerLine.take();
//睡眠指定服务时间,模拟真实场景的服务Customer
TimeUnit.MILLISECONDS.sleep(customer.getServiceTime());
synchronized (this) {
while (!servingCustomerLine) {//被分配做其它事情,则挂起.
wait();
}
}
}
} catch (InterruptedException ex) {
System.out.println(this + "通过中断异常退出");
}
System.out.println(this + "Terminating");
}
//调用这个方法意味着该Teller对象被分配去做其它事情
public synchronized void doSomethingElse() {
customerServed = 0;//已服务数置为0
servingCustomerLine = false; //设定标志,使当前线程在run方法中进入挂起状态
}
//被分配到服务customerLine
public synchronized void serveCustomerLine() {
servingCustomerLine = true;//修改为服务顾客的标志值
notifyAll();//通知挂起线程
}
//按以服务顾客数确定Teller的优先级,服务数越少则优先级越高,置于PriorityQueue队列头部
@Override
public synchronized int compareTo(Teller other) {
//小于返回-1,等于返回0,大于返回1
return (customerServed < other.customerServed) ? -1 : ((customerServed == other.customerServed) ? 0 : 1);
}
//重写toString()方法
public String toString() {
return "Teller" + id;
}
}
//服务管理和调度Teller的类
//这个TellerManager类是各种活动的中心,它跟踪所有的出纳员以及等待服务的顾客
//从adjustTellerNumber()中可以看到,它会根据实际情况调整服务CustomerLine的Teller数量
//以期达到最优出纳员的数目
class TellerManager implements Runnable {
private ExecutorService exec; //负责启动Teller线程
private CustomerLine customerLine;
//按服务顾客数由少到多进行排列
//保证调度时,优先取出服务顾客数最少的出纳员来进行服务,以保证公平性。
private PriorityQueue<Teller> workingTellers = new PriorityQueue<>();//存放服务顾客的队列
//正在做其它事情的Teller队列
private Queue<Teller> tellersDoingOtherThings = new LinkedList<Teller>();
private int adjustmentPeriod; //调度时间
public TellerManager(ExecutorService exec, CustomerLine customerLine, int adjustmentPeriod) {
this.exec = exec;
this.customerLine = customerLine;
this.adjustmentPeriod = adjustmentPeriod;
//第一次执行先在构造器中,为顾客等待队列分配一个Teller进行服务
Teller teller = new Teller(customerLine);
exec.execute(teller);
workingTellers.add(teller);//添加到服务顾客的队列中
}
//通过当前customerLine中的顾客数以及正在工作的Teller人数的比例关系
//确定是否要加/减Teller的数目
public void adjustTellerNumber() {
//如果customerLine队列过长,则增加服务的Teller
if (customerLine.size() / workingTellers.size() > 2) {//顾客等待队列超过服务队列的两倍
//如果在做其它事的Teller则从中抽调出人来,否则重新分配一个Teller
if (tellersDoingOtherThings.size() > 0) {//做其他事的队列不为空
Teller teller = tellersDoingOtherThings.remove();//弹出队列的第一个元素
teller.serveCustomerLine();//重新去进行服务顾客
workingTellers.add(teller);
return;
}
//做其他事的队列为空,则重新分配一个Teller去服务
Teller teller = new Teller(customerLine);
exec.execute(teller);
workingTellers.add(teller);
return;
}
//当前Tellers过多时,抽调一些去做其它工作
if (workingTellers.size() > 1 && customerLine.size() / workingTellers.size() < 2) {//workingTellers.size()>1 确保不会出现除0错误,而且程序中要求服务队列至少存在一个teller
reassignOneTeller();//将服务数较少的Teller转去做其他事
//如果顾客等待队列为空,则只需留下一个Teller进行服务,其余全部转去做其他事
if (customerLine.size() == 0) {
while (workingTellers.size() > 1) {
reassignOneTeller();
}
}
}
}
//将服务数较少的Teller转去做其他事
private void reassignOneTeller() {
//从工作队列的队头取一个Teller出来
Teller teller = workingTellers.poll();
teller.doSomethingElse();//让他去做其它工作
tellersDoingOtherThings.offer(teller);//将teller插入做其他事队列的队尾
}
public void run() {
try {
while (!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(adjustmentPeriod);//等待一定的调度时间
//按当前情况进行动态调整
adjustTellerNumber();
//打印当前的customerLine和workingTeller的情况
//随着customerLine的变化,workingTeller的人数也在不断变化
System.out.print(customerLine + "{");//顾客等待队列的情况
for (Teller teller : workingTellers) {//服务队列的情况
System.out.print(teller.toString() + " ");
}
System.out.println("}");
}
} catch (InterruptedException ex) {
System.out.println(this + "通过中断异常退出");
}
System.out.println(this + "terminating");
}
}
public class BankTellerSimulation {
static final int SIZE = 50;//顾客队列的最大长度
static final int PERIOD = 1000;//调度时间(若时间过长,将导致顾客队列超出最大长度,从而抛出异常)
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
CustomerLine customerLine = new CustomerLine(SIZE);
exec.execute(new CustomerGenerator(customerLine));//不断向队列中增加顾客
exec.execute(new TellerManager(exec, customerLine, PERIOD));//执行调度,合理调配服务人员
System.out.println("Press 'Enter' to exit");//退出程序提示
System.in.read();//等待输入
exec.shutdownNow();//退出任务
}
}
输出结果:
[200][207]{Teller0 Teller1 }
[861][258][140][322]{Teller0 Teller1 }
[575][342][804][826][896][984]{Teller0 Teller1 Teller2 }
[984][810][141][12][689][992][976][368][395][354]{Teller0 Teller1 Teller2 Teller3 }
[395][354][222][687][634][317][242][698]{Teller0 Teller1 Teller2 Teller3 }
[698][899][665][909][209][158][273][894][984]{Teller0 Teller1 Teller2 Teller3 }
[984][533][8][328][779][882][37]{Teller3 Teller1 Teller2 }
[882][37][871][17][828][696][994][419][738][434][106][718][965][416][217][677]{Teller3 Teller1 Teller2 Teller0 }
[419][738][434][106][718][965][416][217][677][146][552][244][111][934][843][585][966]{Teller3 Teller1 Teller2 Teller0 Teller4 }
[244][111][934][843][585][966][615][913][921][213][409][983]{Teller3 Teller1 Teller2 Teller0 Teller4 }
[913][921][213][409][983][158][225][326][851][198][505]{Teller3 Teller1 Teller2 Teller0 Teller4 }
[332][310][933][110][371][529][830]{Teller4 Teller1 Teller2 Teller0 }
[236][824][907][381][458][313][688]{Teller0 Teller1 Teller2 }
[458][313][688][883][996][813][147][790]{Teller0 Teller1 Teller2 }
[813][147][790][265][846][990][122][794][391]{Teller0 Teller1 Teller2 Teller3 }
[391][179][931][378][303][334]{Teller3 Teller1 Teller2 }
[334][64][465][517][223][483][956]{Teller3 Teller1 Teller2 }
[877][799][94][473][939]{Teller2 Teller1 }
[473][939][16][379][631][350][126][911][369]{Teller2 Teller1 Teller4 }
[911][369][156][785][330][976][54][222][286]{Teller2 Teller1 Teller4 Teller0 }