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. 總結

與交通燈管理系統相比,本項目不需設計狀態機,因爲窗口的功能不是按固定規律切換的。但是增加了一個難點,就是多個消耗隊列的線程(也就是業務辦理線程)之間的互相干擾問題。爲了保證隊列空判斷語句和移除隊列元素的語句連貫執行,需要給消耗線程的語句塊上鎖,而鎖必須是所有消耗線程共用的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章