笔者观看了张老师关于银行业务调度系统的视频讲解,按照要求自己重新编写了程序。
1. 项目需求
模拟实现银行业务调度系统逻辑,具体需求如下:
(1)银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。
(2)有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。
(3)异步随机生成各种类型的客户,生成各类型用户的概率比例为:
VIP客户 :普通客户 :快速客户 = 1 :6 :3。
(4)客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。
(5)各类型客户在其对应窗口按顺序依次办理业务。
(6)当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。
(7)随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。
(8)不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。
(1)银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。
(2)有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。
(3)异步随机生成各种类型的客户,生成各类型用户的概率比例为:
VIP客户 :普通客户 :快速客户 = 1 :6 :3。
(4)客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。
(5)各类型客户在其对应窗口按顺序依次办理业务。
(6)当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。
(7)随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。
(8)不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。
2. 需求分析
(1)点:每个业务办理流程都是一个线程。所以程序应该有4个普通业务办理线程,1个快速业务办理线程和1个VIP业务办理线程同时运行;
(2)点:创建三个用arraylist实现的队列,分别为普通客户队列,快速客户队列和VIP客户队列,相应客户将被放到这些队列里;
(3)点:异步随机生成三种类型的顾客,需要建立三个客户生成线程,分别生成普通顾客,快速顾客和VIP顾客。在生成时检验一个随机数是否处于某范围内,若在则生成成功,若不在则生成失败。三个生成线程的生成时间间隔是一样的。比起张老师控制生成间隔来控制概率的方法,这个方法更符合生活实际,毕竟新老客户出现的时间间隔不是固定的,只要在足够长的时间内三种客户的数量比例大体符合要求即可;
(4)点:客户生成线程需要同时生成每个客户的业务处理时间,该时间是指定范围内的随机数。当业务办理流程取到客户后,按照客户的业务处理时间用线程休眠语句停顿对应时间;
(5)点:业务办理线程需要从对应的等待队列中取新客户;
(6)点:快速业务办理线程和VIP业务办理线程在取客户时要检查本业务的等待队列是否为空。若为空则要从普通客户队列里取客户;
(7)点:(3)点和(4)点即满足;
(8)点:建立监视器线程,当三个等待队列中的任何一个变化时,即打印出时间和变化后的情况。这样每个客户的出现时间,业务处理开始时间和结束时间都可以据此得出。
综上,银行业务调度系统的逻辑与交通灯系统有相似之处,但是也有一个很大的不同:银行的多个窗口都在消耗同一个等待队列,而交通灯系统中的一盏灯只是消耗一个等待队列。因此要特别注意业务处理线程的串扰问题。为了保证安全,需要用锁锁住其中的判断和remove队列语句。另外,在张老师提供的源码中用到了工厂模式和java.util.concurrent包,这里为了简便用的是常规方法生成对象和多线程。
(2)点:创建三个用arraylist实现的队列,分别为普通客户队列,快速客户队列和VIP客户队列,相应客户将被放到这些队列里;
(3)点:异步随机生成三种类型的顾客,需要建立三个客户生成线程,分别生成普通顾客,快速顾客和VIP顾客。在生成时检验一个随机数是否处于某范围内,若在则生成成功,若不在则生成失败。三个生成线程的生成时间间隔是一样的。比起张老师控制生成间隔来控制概率的方法,这个方法更符合生活实际,毕竟新老客户出现的时间间隔不是固定的,只要在足够长的时间内三种客户的数量比例大体符合要求即可;
(4)点:客户生成线程需要同时生成每个客户的业务处理时间,该时间是指定范围内的随机数。当业务办理流程取到客户后,按照客户的业务处理时间用线程休眠语句停顿对应时间;
(5)点:业务办理线程需要从对应的等待队列中取新客户;
(6)点:快速业务办理线程和VIP业务办理线程在取客户时要检查本业务的等待队列是否为空。若为空则要从普通客户队列里取客户;
(7)点:(3)点和(4)点即满足;
(8)点:建立监视器线程,当三个等待队列中的任何一个变化时,即打印出时间和变化后的情况。这样每个客户的出现时间,业务处理开始时间和结束时间都可以据此得出。
综上,银行业务调度系统的逻辑与交通灯系统有相似之处,但是也有一个很大的不同:银行的多个窗口都在消耗同一个等待队列,而交通灯系统中的一盏灯只是消耗一个等待队列。因此要特别注意业务处理线程的串扰问题。为了保证安全,需要用锁锁住其中的判断和remove队列语句。另外,在张老师提供的源码中用到了工厂模式和java.util.concurrent包,这里为了简便用的是常规方法生成对象和多线程。
3. 模块分析
整个程序涉及到一个接口(存储常数)和10个类(main类,客户类,等待队列类,普通客户生成类,快速客户生成类,VIP客户生成类,普通业务办理类,快速业务办理类,VIP业务办理类,监视器类)。
3.1 接口
将一些常数写进接口里,便于后面的模块共享里面的常数。
import java.util.*;
//常数标签
interface Tag
{
//顾客出现的时间间隔(秒)
public final double CUSTOMER_INTERVAL = 0.1;
//顾客所需服务时间最小值(秒)
public final double JOB_TIME_MIN = 0.4;
//最大值(秒)
public final double JOB_TIME_MAX = 1;
}
3.2 客户类
//顾客类,包括每个顾客所需的服务时间
class Customer
{
double time;
public Customer(double time)
{
this.time = time;
}
}
3.3 等待队列类
//等待队列类,包括普通顾客的等待队列,快速顾客的等待队列和VIP顾客的等待队列
class Queue
{
ArrayList<Customer> arrayNormal = new ArrayList<Customer>();
ArrayList<Customer> arrayFast = new ArrayList<Customer>();
ArrayList<Customer> arrayVip = new ArrayList<Customer>();
}
3.4 普通客户生成类
class Normal implements Tag, Runnable
{
Queue queue;
public Normal(Queue queue)
{
this.queue = queue;
}
@Override
public void run()
{
long time = System.currentTimeMillis();
while(true)
{
int odd = new Random().nextInt(10);
//每次试图产生普通顾客有60%成功率
if(odd < 6)
{
//放入普通客户等待队列
queue.arrayNormal.add(new Customer(JOB_TIME_MIN + (JOB_TIME_MAX - JOB_TIME_MIN) * new Random().nextDouble()));
}
//每次尝试产生后停顿一段时间
try
{
Thread.sleep((long)(CUSTOMER_INTERVAL * 1000));
}
catch(Exception e){}
}
}
}
3.5 快速客户生成类
//产生快速顾客的线程
class Fast implements Tag, Runnable
{
Queue queue;
public Fast(Queue queue)
{
this.queue = queue;
}
@Override
public void run()
{
while(true)
{
int odd = new Random().nextInt(10);
//每次试图产生普通顾客有30%成功率
if(odd < 3)
{
//放入快速客户等待队列
queue.arrayFast.add(new Customer(JOB_TIME_MIN));
}
//每次尝试产生后停顿一段时间
try
{
Thread.sleep((long)(CUSTOMER_INTERVAL * 1000));
}
catch(Exception e){}
}
}
}
3.6 VIP客户生成类
//产生VIP客户的线程
class Vip implements Tag, Runnable
{
Queue queue;
public Vip(Queue queue)
{
this.queue = queue;
}
@Override
public void run()
{
while(true)
{
//每次试图产生普通顾客有10%成功率
int odd = new Random().nextInt(10);
if(odd < 1)
{
//放入VIP客户等待队列
queue.arrayVip.add(new Customer(JOB_TIME_MIN + (JOB_TIME_MAX - JOB_TIME_MIN) * new Random().nextDouble()));
}
//每次尝试产生后停顿一段时间
try
{
Thread.sleep((long)(CUSTOMER_INTERVAL * 1000));
}
catch(Exception e){}
}
}
}
3.7 普通业务办理类
//普通窗口的业务办理线程
class NormalTask implements Tag, Runnable
{
Queue queue;
Object lock;
boolean wait;
double time;
public NormalTask(Queue queue, Object lock)
{
this.queue = queue;
this.lock = lock;
}
@Override
public void run()
{
while(true)
{
wait = false;
//remove动作要求公共锁,防止其它线程干扰
synchronized(lock)
{
if(queue.arrayNormal.size() != 0)
{
//获取该顾客办理业务所需时间
time = queue.arrayNormal.get(0).time;
//从等待队列中移除该顾客
queue.arrayNormal.remove(0);
wait = true;
}
}
if(wait)
{
try
{
//办理业务耗时
Thread.sleep((long)(time * 1000));
}
catch(Exception e){}
}
}
}
}
3.8 快速业务办理类
//快速窗口的业务办理线程
class FastTask implements Tag, Runnable
{
Queue queue;
Object lock;
boolean wait;
double time;
public FastTask(Queue queue, Object lock)
{
this.queue = queue;
this.lock = lock;
}
@Override
public void run()
{
while(true)
{
wait = false;
synchronized(lock)
{
//优先办理快速顾客业务
if(queue.arrayFast.size() != 0)
{
time = queue.arrayFast.get(0).time;
queue.arrayFast.remove(0);
wait = true;
}
//若快速顾客等待队列为空,则办理普通顾客业务
else if(queue.arrayNormal.size() != 0)
{
time = queue.arrayNormal.get(0).time;
queue.arrayNormal.remove(0);
wait = true;
}
}
if(wait)
{
try
{
Thread.sleep((long)(time * 1000));
}
catch(Exception e){}
}
}
}
}
3.9 VIP业务办理类
//VIP窗口的业务办理线程
class VipTask implements Tag, Runnable
{
Queue queue;
Object lock;
boolean wait;
double time;
public VipTask(Queue queue, Object lock)
{
this.queue = queue;
this.lock = lock;
}
@Override
public void run()
{
while(true)
{
wait = false;
synchronized(lock)
{
//优先办理VIP顾客业务
if(queue.arrayVip.size() != 0)
{
time = queue.arrayVip.get(0).time;
queue.arrayVip.remove(0);
wait = true;
}
//若VIP顾客等待队列为空,则办理普通顾客业务
else if(queue.arrayNormal.size() != 0)
{
time = queue.arrayNormal.get(0).time;
queue.arrayNormal.remove(0);
wait = true;
}
}
if(wait)
{
try
{
Thread.sleep((long)(time * 1000));
}
catch(Exception e){}
}
}
}
}
3.10 监视器类
//监控器线程,当顾客等待队列发生变化时打印当前等待队列
class Monitor implements Tag, Runnable
{
Queue queue;
int normal_old = 0;
int fast_old = 0;
int vip_old = 0;
Object lock;
public Monitor(Queue queue, NormalTask normaltask1, NormalTask normaltask2, NormalTask normaltask3, NormalTask normaltask4, FastTask fasttask, VipTask viptask, Object lock)
{
this.queue = queue;
this.lock = lock;
}
@Override
public void run()
{
long time = System.currentTimeMillis();
while(true)
{
synchronized(lock)
{
if(queue.arrayNormal.size() != normal_old || queue.arrayFast.size() != fast_old || queue.arrayVip.size() != vip_old)
{
System.out.println((double)(System.currentTimeMillis() - time)/1000 + "s");
System.out.println("Normal Fast VIP");
System.out.println(queue.arrayNormal.size() + " " + queue.arrayFast.size() + " " + queue.arrayVip.size());
System.out.println("");
normal_old = queue.arrayNormal.size();
fast_old = queue.arrayFast.size();
vip_old = queue.arrayVip.size();
}
}
}
}
}
3.11 main类
public class Bank
{
public static void main(String[] args)
{
//所有业务办理流程共享的锁
Object lock = new Object();
Queue queue = new Queue();
Normal normal = new Normal(queue);
Fast fast = new Fast(queue);
Vip vip = new Vip(queue);
NormalTask normaltask1 = new NormalTask(queue, lock);
NormalTask normaltask2 = new NormalTask(queue, lock);
NormalTask normaltask3 = new NormalTask(queue, lock);
NormalTask normaltask4 = new NormalTask(queue, lock);
FastTask fasttask = new FastTask(queue, lock);
VipTask viptask = new VipTask(queue, lock);
//开启顾客生成线程
new Thread(normal).start();
new Thread(fast).start();
new Thread(vip).start();
//开启业务办理线程
new Thread(normaltask1).start();
new Thread(normaltask2).start();
new Thread(normaltask3).start();
new Thread(normaltask4).start();
new Thread(fasttask).start();
new Thread(viptask).start();
//开启监控器线程
new Thread(new Monitor(queue, normaltask1, normaltask2, normaltask3, normaltask4, fasttask, viptask, lock)).start();
}
}
4. 运行结果
上面的日志显示了三个等待队列随时间的变化情况。
5. 总结
与交通灯管理系统相比,本项目不需设计状态机,因为窗口的功能不是按固定规律切换的。但是增加了一个难点,就是多个消耗队列的线程(也就是业务办理线程)之间的互相干扰问题。为了保证队列空判断语句和移除队列元素的语句连贯执行,需要给消耗线程的语句块上锁,而锁必须是所有消耗线程共用的。