题目:
1. 某银行有至多三个窗口提供服务。
2. 该银行每天至多服务100人次;
3. 初始时,只有一个窗口开放,如果等待人数超过两人(包含正在办理业务的人),才开放下一个窗口。
模拟业务办理过程:
这个小小的问题里面包含几个关键点:
1. 显然三个窗口可以用三个线程来做,那么如何得到三个线程服务的总人数?涉及到多线程数据同步问题。
2. 100人次可能不是一次来的。有可能分几次来,那么某一个或几个窗口会处于等待状态,涉及到线程等待和唤醒问题。
3. 初始并不是一次出来三个窗口,而是根据队列中等待人数多少。那么考虑到扩展性,我要增加到四个或N个窗口呢?涉及到动态创建线程问题。
4. 当服务总人数达到100人次,不可能直接让三个窗口结束任务,总得让人家把业务办完吧?涉及到多线程之间等待问题。
5. 当服务总人数达到100人次,并且三个窗口都结束了任务,如何关闭所有线程?
6. 这是哪家银行?火速报名投简历!!!这才是王道!
自己写的源码如下(github: https://github.com/EdisonXu/interview/tree/master/main/src/main/java/com/edi/interview/bank)
窗口实现类:
package com.edi.interview.bank;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ServiceWindows implements Runnable {
public static Queue<Integer> waitingQueue = new ConcurrentLinkedQueue<Integer>();
private Bank bank;
public ServiceWindows(Bank bank) {
super();
this.bank = bank;
}
public void run() {
Integer current = null;
while(true)
{
do
{
current = waitingQueue.poll();
if(current==null){
System.out.println(Thread.currentThread().getName() + " is waiting task");
bank.getThreadGroup().await(Thread.currentThread().getName());
if(bank.isClosed())
{
//Thread.currentThread().interrupt();
return;
}
}
}while(current == null);
System.out.println(Thread.currentThread().getName() + " is handing request " + current);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.edi.interview.bank;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Bank {
private boolean closed = false;
private int maxTicketNum = 100;
private int waitThreshold = 2;
private int maxServiceWindows = 3;
private int currentServiceWindows = 0;
private int currentTicketNum;
private ServiceWindows sw = null;
private MyThreadGroup threadGroup = null;
public void getTicketAndWait()
{
currentTicketNum++;
if(!closed && (currentTicketNum>maxTicketNum))
{
System.out.println("Reach maximum workload, will close");
this.close();
return;
}
ServiceWindows.waitingQueue.offer(new Integer(currentTicketNum));
if(currentServiceWindows<maxServiceWindows && currentTicketNum>waitThreshold*currentServiceWindows)
{
currentServiceWindows++;
new Thread(threadGroup, sw, "Windows"+currentServiceWindows).start();
System.out.println("Open window " + currentServiceWindows);
}
this.threadGroup.notifyAllThread();
}
public Bank(int maxTicketNum) {
super();
this.maxTicketNum = maxTicketNum;
}
public Bank() {
super();
}
public void open()
{
currentServiceWindows++;
threadGroup = new MyThreadGroup("Bank Windows Group");
this.sw = new ServiceWindows(this);
new Thread(threadGroup, sw, "Windows"+currentServiceWindows).start();
}
public MyThreadGroup getThreadGroup() {
return threadGroup;
}
public void close()
{
closed = true;
System.out.println("Waiting windows to finish services");
while(true)
{
if(ServiceWindows.waitingQueue.size()==0 && this.threadGroup.isAwaiting())
{
threadGroup.notifyAllThread();
//System.out.println("Alive threads: " + threadGroup.activeCount());
if(threadGroup.activeCount()==0)
{
System.out.println("Bank is closed.");
return;
}
}
}
}
public boolean isClosed() {
return closed;
}
class MyThreadGroup extends ThreadGroup
{
Map<String, Object> lockMap = new ConcurrentHashMap<String, Object>();
public MyThreadGroup(String name) {
super(name);
}
public void await(String name)
{
Object lock = new Object();
lockMap.put(name, lock);
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void notifyAllThread()
{
for(Object obj:lockMap.values())
{
synchronized (obj) {
obj.notify();
}
}
}
public boolean isAwaiting()
{
//System.out.println("Map size: " + lockMap.size());
if(lockMap.size()==currentServiceWindows)
{
return true;
}
return false;
}
}
public static void main(String[] args) {
Bank b = new Bank();
b.open();
// wait for the 1st customer
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// begin to handle the first 30 customers
for(int i=0;i<30;i++)
{
b.getTicketAndWait();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// begin to handle the coming 70 customers
for(int i=0;i<99;i++)
{
b.getTicketAndWait();
}
}
}
我对于这个小问题的分析如下:
1. 显然三个窗口可以用三个线程来做,那么如何得到三个线程服务的总人数?涉及到多线程数据同步问题。
A: 本来考虑直接在线程中做数据同步,但是发现无论是用静态变量加锁还是原子操作,要么会出现重复处理问题,要么会导致效率很低。
后来考虑了下,为毛一定要在线程中做?真正的银行也不是把统计数据放在窗口,而是有个单独的取票系统做统计。显然,我可以用一个外面的类来封装计算,线程本身并不统计起处理的总数。如果要单独计算每个窗口处理的业务总数,可以放在线程里面。并且选用ConcurrentLinkedQueue 作为等待队列来保证多线程取数据不会重复和冲突。
2. 100人次可能不是一次来的。有可能分几次来,那么某一个或几个窗口会处于等待状态,涉及到线程等待和唤醒问题。
A: 这个问题显然可以直接靠Object的wait方法来处理,而wait的话就要考虑用哪个对象的wait。
option1: 三个线程共用一个
option2: 三个线程各维护一个
直接看上去,option1就足够用了,直接用等待队列的wait或者单独加个Object均可。
3. 初始并不是一次出来三个窗口,而是根据队列中等待人数多少。那么考虑到扩展性,我要增加到四个或N个窗口呢?涉及到动态创建线程问题。
A: 其实蛮简单,每个窗口等待人数超过2就起新窗口,显然公式为 当前人数 > 窗口数*2 就起新窗口。
4. 当服务总人数达到100人次,不可能直接让三个窗口结束任务,总得让人家把业务办完吧?涉及到多线程之间等待问题。
A: 显然这里我们要维护一个对象来封装所有启动的线程来获得所有线程的对象的状态。该对象无外乎存储一些表示状态的基本数据类型,或直接存储线程信息。
考虑到问题2,要是有个对象能一次性唤醒所有线程,及一次性关闭所有线程,同时还能一次性知道所有线程状态该多好?
JDK提供了ThreadGroup,将一组线程放到一个组里去管理。能满足我们部分需求。
ThreadGroup.interrupt()可以将该组所有线程interrupt。activeCount()方法可以获得当前组内活着的线程总数。
但是,由于我们线程会wait()和sleep() (模拟服务时间),如果interrupt,会打断两个,必须要判断是否处于wait状态,这个判断我没有找到很好的办法。
于是在问题2中选择了option2,三个线程各维护一个对象给wait()操作用,而要关闭时,通过检查维护的三个对象来判断该线程是否处于wait状态。
5. 当服务总人数达到100人次,并且三个窗口都结束了任务,如何关闭所有线程?
A: 不用interrupt方式打断线程,而是靠变量判断后,直接线程直接return, 避免了InterruptedException。 处理exception比较开销较高,而且这里就是要让线程退出。
6. 这是哪家银行?火速报名投简历!!!这才是王道!
A: 求推荐类似银行,每天服务100人……