多线程-银行出纳员仿真

银行出纳员仿真 
问题描述:银行会有很多来办业务的顾客,他们会排队等待服务;对于银行方面他们派出出纳员来服务顾客,如果排队的顾客数量过多,银行就会增加出纳员的数量,如果顾客的数目过少,则减少出纳员的数目;总之要保持一个平衡。

仿真思路:

封装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 }

转载于:https://my.oschina.net/langwanghuangshifu/blog/1941546

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值