程序源码:
http://pan.baidu.com/s/1pKF5vpL
模拟火车票售票系统用来练习多线程再好不过了.
这是第01版,尽量简化难度,我们在之后的版本,一步一步完善.
本节,我们将学习构建一个线程安全且高效的简易版火车票售票系统
题目描述
为了简化数据,我们只考虑以下的火车站点:
模拟列车表如下:
列车 始发点=时间 抵达点=时间 抵达点=时间
D011 北京=00:30 上海=06:10
D012 北京=02:00 上海=08:00
D013 北京=04:30 上海=10:20
D014 北京=08:00 上海=14:00
D015 北京=11:25 上海=18:40
D016 北京=15:00 上海=21:00
D017 北京=17:00 上海=22:40
D018 北京=23:00 上海=次日05:20
G011 北京=07:45 长沙=14:40
G012 北京=00:10 长沙=08:00 广州=10:30
G013 北京=07:15 长沙=14:40 广州=17:10
G014 北京=10:45 长沙=18:40 广州=20:55
G015 北京=13:00 长沙=20:40 广州=23:00
G016 北京=21:30 长沙=次日05:20 广州=次日08:00
G017 北京=22:35 长沙=次日06:25 广州=次日10:00
D021 上海=01:00 北京=07:15
D022 上海=03:35 北京=10:15
D023 上海=06:30 北京=12:30
D024 上海=08:30 北京=14:30
D025 上海=10:50 北京=16:00
D026 上海=15:00 北京=21:10
D027 上海=19:00 北京=23:50
D028 上海=23:30 北京=次日06:00
G021 上海=23:30 长沙=次日07:00
G122 上海=01:20 长沙=10:05 广州=12:35
G123 上海=08:10 长沙=16:45 广州=19:15
G124 上海=11:30 长沙=19:25 广州=21:50
G125 上海=14:10 长沙=22:40 广州=次日01:05
G126 上海=20:25 长沙=次日05:00 广州=次日07:30
G127 上海=22:00 长沙=次日07:00 广州=次日09:35
G031 长沙=21:40 北京=05:30
G032 长沙=11:40 上海=20:30
D031 长沙=06:30 广州=08:55
D031 长沙=08:30 广州=11:00
D031 长沙=13:00 广州=15:30
D031 长沙=18:40 广州=21:00
G042 广州=00:20 长沙=02:45 北京=10:35
G043 广州=07:10 长沙=09:40 北京=17:15
G044 广州=10:30 长沙=12:55 北京=20:50
G045 广州=13:10 长沙=14:40 北京=23:05
G046 广州=21:25 长沙=23:50 北京=次日07:55
G047 广州=22:30 长沙=次日00:55 北京=次日10:10
D041 广州=06:40 长沙=09:00
D041 广州=09:40 长沙=12:00
D041 广州=14:00 长沙=13:40
D041 广州=19:40 长沙=22:20
G142 广州=01:20 长沙=03:45 上海=12:35
G143 广州=08:10 长沙=10:40 上海=19:15
G144 广州=11:30 长沙=13:55 上海=21:50
G145 广州=14:10 长沙=16:40 上海=次日01:05
G146 广州=20:25 长沙=22:50 上海=次日07:30
G147 广州=22:00 长沙=次日00:35 上海=次日09:35
在01版系统中,我们只考虑以下需求:
1.购票: 提供购票操作,每有一次购票操作,票数能正确的减一,并且支持并发购票
2.退票: 支持退票操作,每有一次退票操作,票数正确的加一,并且支持并发退票
设计思路
–>系统启动后,将开启后台线程,线程池
–>后台任务不断获取任务队列中的任务,将任务交给线程池
–>线程池执行任务,涉及到火车票库的操作,交给火车票库处理
–>线程池分析最终结果,将交给out发出(01版暂不考虑,所以只将信息存入了out字符串)
类图结构:
核心代码
源代码下载:
http://pan.baidu.com/s/1pKF5vpL
TicketSystem火车票库
//列车列表
private final Map<Train,Train> list;
//票库
private final Map<Ticket,AtomicInteger> tickets;
//票库镜像
private final Map<Ticket,AtomicInteger> mirror;
//买票
boolean buyTicket(Ticket t){
AtomicInteger i = mirror.getOrDefault(t, null);
if(i!=null){
if(i.decrementAndGet()>=0)
return true;
i.incrementAndGet();
}
return false;
}
//退票
boolean cancelTicket(Ticket t){
AtomicInteger i = mirror.getOrDefault(t, null);
if(i==null)
return false;
i.incrementAndGet();
return true;
}
//放票
public boolean addTicket(Ticket t,int num){
AtomicInteger i = mirror.getOrDefault(t, null);
if(i==null)
return false;
synchronized (i) {
i.set(i.get()+num);
}
return true;
}
SellTicketSystem 售票系统
//票库
private final TicketSystem ts;
//执行任务线程池
private final ExecutorService server;
//任务队列
private final BlockingQueue<Channel> tasks;
//后台线程
private final Thread backThread;
//构造系统
private SellTicketSystem(String path,int num,Date date){
num = num>0&&num<1<<6?num:DEFUALT_THREAD;
this.ts = TicketSystem.create(path,date);
server = Executors.newFixedThreadPool(num);
tasks = new LinkedBlockingQueue<>(num<<3);
backThread = new Thread(){
public void run() {
while(!isInterrupted()){
try{
server.submit(tasks.take());
}catch(InterruptedException e){
System.err.println("获取Channel被中断");
break;
}
}
System.err.println("获取Channel已经停止运行");
}
};
}
//启动
public synchronized static boolean start(String path,int num,Date date){
if(sys==null){
sys = new SellTicketSystem(path,num,date);
sys.backThread.start();
System.err.println("系统安全启动");
return true;
}else{
System.err.println("警告: 系统已经启动,请勿重复启动!");
return false;
}
}
//关闭
public synchronized static boolean shutdown(){
sys.server.shutdown();
sys.backThread.interrupt();
return true;
}
测试
在test.txt中提前写入所有测试的指令
buy G017 2017-06-10 北京 广州
buy G017 2017-06-10 北京 广州
buy G017 2017-06-10 北京 广州
buy G017 2017-06-10 北京 广州
cancel G017 2017-06-10 北京 广州
buy G017 2017-06-10 北京 广州
buy G017 2017-06-10 北京 广州
cancel G017 2017-06-10 北京 广州
buy G017 2017-06-10 北京 广州
buy G016 2017-06-10 北京 广州
buy G016 2017-06-10 北京 广州
......
有三个测试类
1.串行测试类,这个类用于测试串行执行指令的结果是否正确,并将所有结果存储在result.txt中
主要代码:
List<String> list = FileUtils.readLines(new File(path));
List<Channel> clist = new ArrayList<Channel>();
for (String op : list) {
Channel c = new Channel(op);
c.run();
clist.add(c);
}
StringBuilder sb = new StringBuilder();
clist.forEach(e->sb.append(e).append("\r\n"));
FileUtils.write(new File(newPath), sb.toString());
2.单线程测试类, 测试线程池执行指令结果是否正确,并将结果存储在singleThreadTest.txt中
核心代码:
List<String> list = FileUtils.readLines(new File(path));
List<Channel> clist = new ArrayList<Channel>();
for (String op : list) {
Channel c = new Channel(op);
SellTicketSystem.sys().addTask(c);
clist.add(c);
}
Thread.sleep(200);
StringBuilder sb = new StringBuilder();
clist.forEach(e->sb.append(e).append("\r\n"));
FileUtils.write(new File(newPath), sb.toString());
3.对比两个测试结果,相同的行说明执行结果正确,不相同的行,需要人工去检查线程池的执行结果.
多线程执行结果是否正确是非常难以判断的,采用上面的方式能减轻不少的负担.
最终,测试这个系统是比较健壮的了,那么01版的模拟火车票售票系统也就大功告成了.