09 银行业务调度系统

笔者观看了张老师关于银行业务调度系统的视频讲解,按照要求自己重新编写了程序。

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方式展现程序运行结果。

2. 需求分析

(1)点:每个业务办理流程都是一个线程。所以程序应该有4个普通业务办理线程,1个快速业务办理线程和1个VIP业务办理线程同时运行;
(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. 总结

与交通灯管理系统相比,本项目不需设计状态机,因为窗口的功能不是按固定规律切换的。但是增加了一个难点,就是多个消耗队列的线程(也就是业务办理线程)之间的互相干扰问题。为了保证队列空判断语句和移除队列元素的语句连贯执行,需要给消耗线程的语句块上锁,而锁必须是所有消耗线程共用的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章