一、设计策略
1
第一次完成的是一个傻瓜电梯,简单来说,就是来一个请求,就服务一个请求,服务完之后再服务下一个请求,这样循环往复。在完成这一次作业的时候,我对于多线程的理解还不是很深入,一开始在尝试单开一个电梯线程完成这次作业的时候出现了一些bug,我便尝试继续使用单线程的思路,很快就写出了单线程版本的作业,简单来说就是将电梯的所有功能写到一个deal函数中:
- public void deal(PersonRequest r) throws Exception {
- long dis;
- dis = Math.abs(floorn - r.getFromFloor());
- Thread.sleep(dis * runt);
- floorn = r.getFromFloor();
- TimableOutput.println(String.format("OPEN-%d", floorn));
- Thread.sleep(opent);
- TimableOutput.println(
- String.format("IN-%d-%d", r.getPersonId(), floorn));
- Thread.sleep(closet);
- TimableOutput.println(
- String.format("CLOSE-%d", floorn));
- dis = Math.abs(floorn - r.getToFloor());
- Thread.sleep(dis * runt);
- floorn = r.getToFloor();
- TimableOutput.println(String.format("OPEN-%d", floorn));
- Thread.sleep(opent);
- TimableOutput.println(
- String.format("OUT-%d-%d", r.getPersonId(), floorn));
- Thread.sleep(closet);
- TimableOutput.println(String.format("CLOSE-%d", floorn));
- }
在主函数中建立while循环,取到请求之后就调用这个电梯函数进行服务,然后循环往复,直到取到null之后跳出while循环,结束主函数。这样一个单线程版本实现很简单并且不容易出bug,唯一利用到多线程思想的地方是在电梯的deal函数中为了实现等待运行时间调用了Thread.sleep方法。通过所有中测之后,我又继续完成了自己的多线程版本,大体采用的是生产者-消费者模式,虽然也通过了中测,但是总体设计比较混乱,在这里就不做分析了。
2
第二次完成的是一个可捎带的电梯,我的主要思路如下:主线程充当输入线程,再开一个调度器线程,一个电梯线程,输入线程和调度器线程共享一个请求队列A,调度器线程和电梯线程共享一个请求队列B,电梯内部独享一个捎带请求队列C,输入线程要做的事情就是不断取得请求,加入队列A;调度器线程这次只有一个电梯可以指挥,故它要做的事情就是不断从队列A中取出请求加入队列B;而电梯线程要做的事情就是不断从队列B中取出请求进行服务,而具体的服务流程是这样的,首先从B中拿出一个请求作为主请求,然后在完成这个主请求的过程中,每到一层就调用check函数:检查有没有人可以进电梯(要去的楼层和我当前的运行方向相同),有没有人可以出电梯(到达了他的目标楼层),可以进的就加入队列C,可以出的就从队列C中删除掉,最后在完成我的主请求之后,检查队列C是否为空,如果不为空,利用pop函数:不断取队首的请求对象进行服务,直到为空,再次进行while循环,重新取一个主请求……循环往复。
3
这一次的作业实现的是同时管理三部电梯,而且这三部电梯在性能(能够到达的楼层、容量、运行速度)上都有着区别,但我个人在完成这次作业的过程中感觉其实在对于线程的处理并没有什么新的内容,仍然是调度器线程完成分配,电梯线程负责跑,主线程负责输入,各个线程的运行保持独立,在共享资源的访问上避免冲突,与第二次作业没有很大的区别,但当然也不是完全没有区别,在我的设计之中,主要区别有二:
1.本次的作业需要将某些请求拆分,分配给两个电梯完成,拆分出来的两个请求,第一个直接分配给电梯,第二个存在调度器内部的wlist中,等待电梯完成第一部分请求后再进行二次分配,
- public void dis2(Request t, int f, Elevator e1, Elevator e2) {
- Request t1 = new Request(t.getFromFloor(), f, t.getPersonId());
- Request t2 = new Request(f, t.getToFloor(), t.getPersonId());
- t1.setCon(1);
- t2.setEle(e2.getName());
- e1.getLista().add(t1);
- wlist.add(t2);
- }
这就需要电梯给调度器传递一个信息,告知调度器:我已完成第一部分任务,你可进行二次分配了。
- public void checkt(Request t) {
- if (t.getCon() == 0 || t.getCon() == 2) {
- return;
- } else {
- manager.trans(t.getPersonId());
- return;
- }
- }
2.在结束程序问题上,我的第二次和第三次作业也有着较大的区别。我的第二次作业中是这样做的:主线程(输入线程)在读到null之后调用manager.end()通知调度器:你结束手头上的工作之后就可以结束了,然后自己结束;调度器在while循环的开头进行检查,如果可以结束,并且已经将输入队列中的请求全部分配出去,就调用elevator.end()通知电梯(只有一部):你结束手头上的工作之后就可以结束了,然后自己结束;电梯线程同样在while循环的开头进行检查,如果可以结束,并且已经将请求队列中的所有请求服务完毕,就自己结束,调用systemexit(0),结束整个程序。而第三次作业显然就不能这样做,三部电梯处于平等地位,不能有哪一部拥有结束程序的权力,所以我采用方式是调度器在没有新请求,并且wlist为空之后通知各个电梯,电梯接到通知之后自身检查,发现已经完成所有任务之后再次通知调度器(调度器中的over变量自增),调度器在接到三部电梯各自的结束信号之后结束整个程序。
- this.elevator1.endIn();
- this.elevator2.endIn();
- this.elevator3.endIn();
- while (over != 3) {
- try {
- Thread.sleep(1);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- System.exit(0);
二、程序分析
1
类图:
这次作业我就只有两个类,main类负责接受输入,然后调用elevator类的deal方法进行服务。比较清晰。
规模:
复杂度:
Complexity metrics | 星期一 | 22 四月 2019 21:28:40 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
Elevator.deal(PersonRequest) | 1 | 1 | 1 |
Main.main(String[]) | 3 | 4 | 4 |
Class | OCavg | WMC | |
Elevator | 1 | 1 | |
Main | 3 | 3 |
2
类图:
这次作业我有四个类,main类负责接受输入,requestlist是我自己实现的一个线程安全的请求队列,其中的get方法就是从队列头取元素并删除(先来先服务),add方法就是将新的请求加入队尾。manager类进行调度,elevator进行服务。逻辑结构比较清晰,但是各个线程之间是通过引用的方式进行通信的,其实还有更好的方法可以采用。
规模:
复杂度:
Complexity metrics | 星期一 | 22 四月 2019 21:29:42 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
Elevator.check() | 1 | 1 | 1 |
Elevator.checkin() | 1 | 10 | 10 |
Elevator.checkout() | 1 | 4 | 4 |
Elevator.endIn() | 1 | 1 | 1 |
Elevator.getLista() | 1 | 1 | 1 |
Elevator.mainrun(PersonRequest) | 5 | 3 | 5 |
Elevator.mfcheck() | 1 | 6 | 6 |
Elevator.move() | 3 | 3 | 5 |
Elevator.popn() | 3 | 2 | 3 |
Elevator.run() | 3 | 9 | 10 |
Elevator.samedir(int,int) | 3 | 1 | 5 |
Main.main(String[]) | 3 | 3 | 4 |
Manager.endIn() | 1 | 1 | 1 |
Manager.run() | 3 | 6 | 7 |
Manager.setElevator(Elevator) | 1 | 1 | 1 |
Manager.setInlist(RequestList) | 1 | 1 | 1 |
Manager.setRulist(RequestList) | 1 | 1 | 1 |
RequestList.add(PersonRequest) | 1 | 1 | 1 |
RequestList.copy() | 1 | 1 | 1 |
RequestList.get() | 1 | 3 | 3 |
RequestList.getList() | 1 | 1 | 1 |
RequestList.isempty() | 2 | 1 | 2 |
RequestList.remove(PersonRequest) | 1 | 1 | 1 |
Class | OCavg | WMC | |
Elevator | 3.73 | 41 | |
Main | 3 | 3 | |
Manager | 1.6 | 8 | |
RequestList | 1.33 | 8 |
时序图:
3
类图:
为了保证输出线程安全创建了Out类,其他和第二次类似。
规模:
复杂度:
Complexity metrics | 星期一 | 22 四月 2019 21:30:40 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
Elevator.Elevator(char,int,int,int,int[],Out) | 1 | 1 | 1 |
Elevator.able(Request) | 2 | 2 | 3 |
Elevator.can() | 2 | 1 | 2 |
Elevator.check() | 1 | 1 | 1 |
Elevator.checkin() | 1 | 12 | 12 |
Elevator.checkout() | 1 | 4 | 4 |
Elevator.checkt(Request) | 2 | 3 | 3 |
Elevator.chosem(RequestList) | 1 | 2 | 3 |
Elevator.contain(int[],int) | 3 | 1 | 3 |
Elevator.endIn() | 1 | 1 | 1 |
Elevator.getFable() | 1 | 1 | 1 |
Elevator.getLista() | 1 | 1 | 1 |
Elevator.getName() | 1 | 1 | 1 |
Elevator.getNum() | 1 | 1 | 1 |
Elevator.getNuma() | 1 | 1 | 1 |
Elevator.mainrun(Request) | 5 | 3 | 5 |
Elevator.mfcheck() | 1 | 7 | 7 |
Elevator.move() | 3 | 3 | 5 |
Elevator.popn() | 3 | 2 | 3 |
Elevator.run() | 3 | 9 | 10 |
Elevator.samedir(int,int) | 3 | 1 | 5 |
Elevator.setM(Manager) | 1 | 1 | 1 |
Main.main(String[]) | 3 | 3 | 4 |
Manager.Manager(Elevator,Elevator,Elevator,RequestList) | 1 | 1 | 1 |
Manager.coable(Request,int[],int[],int[]) | 2 | 3 | 3 |
Manager.contain(int[],int) | 3 | 1 | 3 |
Manager.dis2(Request,int,Elevator,Elevator) | 1 | 1 | 1 |
Manager.dispatch(Request) | 1 | 10 | 13 |
Manager.eleOver() | 1 | 1 | 1 |
Manager.endIn() | 1 | 1 | 1 |
Manager.isb(int,int,int) | 3 | 1 | 5 |
Manager.mid(int,int,int[]) | 3 | 2 | 3 |
Manager.run() | 3 | 11 | 11 |
Manager.share(Elevator,Elevator) | 3 | 1 | 9 |
Manager.trans(int) | 4 | 4 | 7 |
Out.print(String) | 1 | 1 | 1 |
Request.Request(int,int,int) | 1 | 1 | 1 |
Request.getCon() | 1 | 1 | 1 |
Request.getEle() | 1 | 1 | 1 |
Request.getFromFloor() | 1 | 1 | 1 |
Request.getPersonId() | 1 | 1 | 1 |
Request.getToFloor() | 1 | 1 | 1 |
Request.setCon(int) | 1 | 1 | 1 |
Request.setEle(char) | 1 | 1 | 1 |
RequestList.add(Request) | 1 | 1 | 1 |
RequestList.copy() | 1 | 1 | 1 |
RequestList.get() | 1 | 3 | 3 |
RequestList.getList() | 1 | 1 | 1 |
RequestList.isempty() | 2 | 1 | 2 |
RequestList.remove(Request) | 1 | 1 | 1 |
RequestList.size() | 1 | 1 | 1 |
Class | OCavg | WMC | |
Elevator | 2.68 | 59 | |
Main | 3 | 3 | |
Manager | 3.67 | 44 | |
Out | 1 | 1 | |
Request | 1 | 8 | |
RequestList | 1.29 | 9 |
时序图:
S.O.L.I.D分析
单一责任原则:调度器只管调度,电梯只负责运行,一二次作业没有问题,第三次作业中,由于有的请求需要拆分,导致电梯需要判断某个刚刚出门的乘客是不是所谓“第一步请求”,如果是的话需要通知调度器,一定程度上违背了该原则。
开放封闭原则:第二次作业中没有考虑到第三次会出现拆分等要求,所以可扩展性较差;第三次作业进行了完善 ,可以轻松地扩展出新型电梯。
里氏替换原则:没有类的父子关系。不违背里氏替换原则(LSP)原则。
接口分离原则:仅使用了Runnable一个接口。不违背分离原则(ISP)原则。
依赖倒置原则:高层模块“调度器”对于不同的底层模块“电梯”都能够同样地执行。符合依赖倒置原则(DIP)原则。
三、Bug分析
1
公测和互测未发现bug
2
公测和互测未发现bug
3
公测发现一个bug,表现为电梯超载,原因在于,我设计的电梯的每一次运行之前都要选择一个主请求,在完成主请求的过程中进行捎带,而虽然我在每一次试图捎带之前都判断了容量以确定是否可以捎带,但在主请求人员进入电梯之前我没有进行判断,所以导致某一次之中,在主请求人员进入电梯之前,即电梯前往主请求人员出发楼层的运行过程中,我的电梯已经捎带上了足够多的人导致电梯满了,这时主请求直接进入,导致电梯超载。解决方法是直接将各电梯的理论容量减一,这样就可以保证始终给我的主请求留出空间。
四、互测策略
这一单元的互测其实感觉是比较困难的,基本每次都是一个“尝试阅读代码找bug-找不出来-直接提交测试数据盲狙”的过程,只有在第二次作业的时候侥幸发现了一位同学的Bug,通过阅读他的代码,我发现他的电梯在完成主请求的第二阶段任务时,忘了将可以被捎带的请求从请求队列中删除,导致他的电梯在某种情况下会出现一个人两次进入电梯的情况。其他两次互测均未能发现Bug。
五、心得体会
1.架构最重要。
我的第一次电梯作业由于理解不到位写的非常混乱,导致我的第二次作业从头做起,耗费了相当大的精力,但是也正是因为这样,我的第二次作业最后产生的架构就比较好,一方面它比较安全,不会出现死锁、访问冲突、超时等各种问题;另一方面它十分清晰明了,这给我的第三次作业带来了巨大的便利。
2.最优可能-平均优化。
在第二次第三次的电梯作业的优化过程中,我发现:几乎没有哪一种算法是可以保证我在任何情况下的运行时间都最小的,因此我们的优化只能做到对于大多数的运行过程有一个较好的表现,在平均时间上占有优势,这其实也符合一般生活中的规律。