黑马程序员-JAVA-银行业务调度系统

来源:互联网 发布:login.php 编辑:程序博客网 时间:2024/05/26 12:05

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-


传智播客中有一个面试题目项目就是这个银行业务调度系统,今天也来实现一把。

需求分析

  1. 这家银行有6个业务窗口,其中4个是普通窗口,1个快速窗口,1个VIP窗口,银行的客户比例为普通:快速:VIP=6:3:1,各个类型用户安排到对应窗口,快速和VIP窗口在空闲时也可以服务普通用户,用户业务办理时间是随机的,有最小值,但快速用户永远是最小值。模拟无需GUI,通过log显示结果
  2. 假定最小业务办理时间为30秒,最大600秒
  3. 快速和VIP窗口仅在普通窗口全忙时才接收普通用户
  4. 用户通过取号器来排队,窗口就绪后由号码排队系统分配客户

第一轮设计

  1. 通过一个模拟执行程序执行,执行程序 生成一个银行(6个窗口),一个用户模拟(取号),为了方便,采用100倍速模拟。
  2. 业务窗口有3种类型,行为上并无不同,仅仅标记的用户偏向不同,采用一个enum业务类型来标识。
  3. 用户有明确的业务目的,也就视为:有特定的业务办理时间(假定银行窗口业务员素质相同),有特定的业务类型(普通/快速/VIP),进入银行就有自己的排队号码(未进入设定为-1)。
  4. 业务类型有最小和最大办理时间,普通和VIP为(30,600),快速为(30,30)
  5. 银行取号系统负责给用户分配排队号码,并给空闲的窗口分配客户。

第二轮设计

  1. 窗口应该知道当前服务的客户,窗口应该属于银行,窗口应该有准备就绪开始,计划停止(服务完当前客户)的方法,以通知取号系统。
  2. 取号系统应该在没有排队用户时阻塞,取号系统应该为每个窗口尽快分配用户,按:用户采用3个阻塞队列,快速/VIP窗口轮询特有队列和普通队列。普通窗口直接阻塞取普通队列

实现

代码如下:
业务类型

/** * 银行业务分类 * @author lz * */public enum BusinessType {    /**     * 普通业务 耗时30秒到10分钟     */    Normal(30,600),    /**     * 快速业务 耗时30秒     */    Quick(30,30),    /**     * VIP业务 耗时30秒到10分钟      */    VIP(30,600);    /**     * 最小和最大耗时     */    public int minTime,maxTime;    private BusinessType(int minTime, int maxTime) {        this.minTime = minTime;        this.maxTime = maxTime;    }}

客户

/** * 银行客户 * @author lz * */public class BankUser {    /**     * 来办业务的类型     */    public final BusinessType biz;    /**     * 业务耗时     */    public final int bizTime;    /**     * 排队号码     */    public String queueNum;    @Override    public String toString() {        return "BankUser [biz=" + biz + ", bizTime=" + bizTime + ", queueNum="                + queueNum + "]";    }    public String shortInfo() {        return queueNum+"("+bizTime+")";    }    public BankUser(BusinessType biz, int bizTime) {        super();        this.biz = biz;        this.bizTime = bizTime;    }    /**     * 进入银行排队     * @param bank 银行     */    public void enQueue(Bank bank){        this.queueNum=bank.offerNum(this);        Logger.getLogger("BankQueue").info("新客户:"+this);    }}

银行,包括窗口和调度系统

import java.time.Instant;import java.util.ArrayList;import java.util.List;import java.util.concurrent.BlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;import java.util.logging.Logger;import static cc.sisel.util.Quic.*;/** * 银行抽象,包括窗口和业务调度系统 默认构造包含6个窗口 其中一个VIP一个快速,其他普通 *  * @author lz * */public class Bank implements Runnable {    final BankUserHandler banksys;    List<BusinessWindow> servWindows;    public Bank(BankUserHandler banksys, List<BusinessWindow> servWindows) {        super();        this.banksys = banksys;        this.servWindows = servWindows;    }    public Bank() {        super();        this.banksys = new BankUserHandler();        this.servWindows = new ArrayList<Bank.BusinessWindow>(6);        this.servWindows.add(new BusinessWindow(BusinessType.VIP));        this.servWindows.add(new BusinessWindow(BusinessType.Quick));        this.servWindows.add(new BusinessWindow(BusinessType.Normal));        this.servWindows.add(new BusinessWindow(BusinessType.Normal));        this.servWindows.add(new BusinessWindow(BusinessType.Normal));        this.servWindows.add(new BusinessWindow(BusinessType.Normal));    }    @Override    public void run() {        ExecutorService exes = Executors.newFixedThreadPool(servWindows.size());        for (BusinessWindow businessWindow : servWindows) {            exes.execute(businessWindow);            businessWindow.readyToService();        }        // 演示用,控制台输出客户状态        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(                (() -> sp("等待客户:" + this.banksys.watingSum() + " 今日客户总数:"                        + this.banksys.cameUserSum())), 1, 5, TimeUnit.SECONDS);    }    /**     * 取号     *      * @param bankUser     *            取号的用户     * @return 号码条     */    public String offerNum(BankUser bankUser) {        return this.banksys.accept(bankUser);    }    /**     * 业务调度系统 负责分派客户到窗口 并监视银行中等待客户的队列 记录今日来访的客户计数 生成号码条     *      * @author lz     *     */    /**     * @author lz     *     */    public class BankUserHandler {        BlockingQueue<BankUser> normalQueue = new LinkedBlockingQueue<BankUser>(),                quickQueue = new LinkedBlockingQueue<BankUser>(),                vipQueue = new LinkedBlockingQueue<BankUser>();        private int vipn = 0, quickn = 0, normaln = 0;        public int watingSum() {            return this.normalQueue.size() + this.quickQueue.size()                    + this.vipQueue.size();        }        public int cameUserSum() {            return this.normaln + this.quickn + this.vipn;        }        /**         * 生成号码条         *          * @param bankUser         *            用户         * @return 号码条         */        private String accept(BankUser bankUser) {            switch (bankUser.biz) {            case VIP:                this.vipQueue.offer(bankUser);                return "V" + vipn++;            case Quick:                this.quickQueue.offer(bankUser);                return "Q" + quickn++;            case Normal:                this.normalQueue.offer(bankUser);                return "N" + normaln++;            }            return null;        }        /**         * 为窗口分派用户,若当前没有则等待1秒         *          * @param businessWindow         *            待分配的窗口         * @return 客户         */        public BankUser offerUser(BusinessWindow businessWindow) {            BankUser offer = null;            while (offer == null) {                switch (businessWindow.offerType) {                case VIP:                    return this.offerVIP();                case Quick:                    return this.offerQuick();                case Normal:                    return this.offerNormal();                }                ts(10);            }            return offer;        }        /**         * 为快速窗口查找匹配的客户         *          * @return 合适的客户         */        private BankUser offerQuick() {            if (!this.quickQueue.isEmpty()) {                try {                    return this.quickQueue.take();                } catch (InterruptedException e) {                    e.printStackTrace();                }                return null;            } else if (!this.normalQueue.isEmpty()) {                try {                    return this.normalQueue.take();                } catch (InterruptedException e) {                    e.printStackTrace();                }                return null;            }             return null;        }        /**         * 为VIP窗口查找匹配的客户         *          * @return 合适的客户         */        private BankUser offerVIP() {            if (!this.vipQueue.isEmpty()) {                try {                    return this.vipQueue.take();                } catch (InterruptedException e) {                    e.printStackTrace();                }                return null;            } else if (!this.normalQueue.isEmpty()) {                try {                    return this.normalQueue.take();                } catch (InterruptedException e) {                    e.printStackTrace();                }                return null;            }             return null;        }        /**         * 为普通窗口查找匹配的客户         * 这个方法是阻塞的         * @return 合适的客户         */        private BankUser offerNormal() {            try {                return this.normalQueue.take();            } catch (InterruptedException e) {                e.printStackTrace();            }            return null;        }    }    /**     * 服务窗口     * @author lz     *     */    public class BusinessWindow implements Runnable {        /**         * 窗口序号         */        public final int serial;        /**         * 是否接续服务         */        private boolean keepWorking;        /**         * 当前服务客户         */        private BankUser currentServing;        /**         * 窗口服务类型         */        public final BusinessType offerType;        public BusinessWindow(BusinessType offerType) {            super();            this.offerType = offerType;            this.serial = Bank.this.servWindows.size();        }        @Override        public String toString() {            return "Window[" + offerType.toString().charAt(0) + serial + "]";        }        public String shortInfo() {            return "W[" + offerType.toString().charAt(0) + serial + "]";        }        /**         * 就绪,可以服务         */        public void readyToService() {            this.keepWorking = true;        }        /**         * 停止,服务完成当前用户就停止         */        public void scheduleStop() {            this.keepWorking = false;        }        /**         * @return 是否正在服务         */        public boolean isBusy() {            return currentServing != null;        }        /**         * 服务过程         * 服务完成会使当前客户离开         */        private void handle() {            if (currentServing != null) {                // Logger.getLogger("BankQueue").info(this.shortInfo() +                // " 开始服务:" + currentServing.shortInfo() + " @" +Instant.now());                // sp(this + " 开始服务:" + currentServing + " @" +Instant.now());                ts(currentServing.bizTime * 10);                 Logger.getLogger("BankQueue").info(this.shortInfo() +                 " 服务完毕:" + currentServing.shortInfo() + " @" +Instant.now());                sp(this + " 服务完毕:" + currentServing + " @" + Instant.now());                this.currentServing = null;            }        }        /**         * 取一个用户         */        private void next() {            this.currentServing = Bank.this.banksys.offerUser(this);        }        @Override        public void run() {            while (true) {                if (this.keepWorking) {                    if(this.currentServing == null){                        this.next();                    }                    this.handle();                } else {                    ts(1000);                }            }        }    }}

模拟器,包括用户生成器

/** * 模拟器 * @author lz * */public class Simulator {    public static void main(String[] args) {        Logger.getLogger("BankQueue").setLevel(Level.ALL);        Bank bank = new Bank();        new Thread(bank).start();        BankUserGenerator gen=new BankUserGenerator();        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(                (() -> gen.randomGen().enQueue(bank)),                 100,                 300,                 TimeUnit.MILLISECONDS);    }}/** * 模拟银行用户生成器 * @author lz * */class BankUserGenerator{    Random r=new Random();    BankUser randomGen(){        BusinessType biz;        int rnum=r.nextInt(10);        if(rnum>8){            biz=BusinessType.VIP;        }else if(rnum>5){            biz=BusinessType.Quick;        }else{            biz=BusinessType.Normal;        }        return new BankUser(biz, biz.minTime+r.nextInt(biz.maxTime-biz.minTime+1));    }}

分析

这里的快速队列和VIP队列采用了轮询的方式,效率偏低,如果可以,用标记多头阻塞队列是理想的,但是这个容器难度颇大,就没有实现。
而且客户的取号系统基本没有在分配中用上,实际应该维持一个映射,系统应该只对号码操作。
窗口的开关功能虽然实现了,但是没有模拟。

扩展讨论

一个用户在办理业务的时候可能多次往返窗口,这样重新拿号势必麻烦,而银行业务人员有时又需要传递用户,序列操作,最后,这个系统中可以加入用户等待时间统计,预估一个等待时间,提高客户体验,银行的窗口分配策略也可以动态修改就更好了。
这些需求,初步分析,可以通过内部号码生成与插队,动态代理调度系统并传递数据的方式完成,而统计就比较简单了,实现的方式很多。

0 0
原创粉丝点击