OO第二单元总结——电梯
1、设计分析
1.1、单电梯可捎带调度方案
第一次接触多线程,单部电梯采用了单生产者—单消费者的模式,输入线程作为生产者,电梯作为消费者,二者共享一个排队队列hashmap<integer,linkedblockingqueue>记录电梯外等待的人,以及出发楼层,之所以选择hashmap这个容器,是为了实现可捎带的要求。电梯内部设置一个队列,自我管理,记录已经上电梯的人,以及目的地。通过ReentrantLock,Condition实现线程的阻塞,唤起。生产者、消费者共享一个对象,用来结束所有线程。电梯的调度算法采用了LOOK算法。
这次个人觉得可扩展性良好,符合设计模型。
不足之处,电梯内部略复杂。
UML类图
时序图
复杂度分析:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.closedoor() | 1 | 3 | 3 |
Elevator.empty(HashMap<Integer, LinkedBlockingQueue |
2 | 2 | 4 |
Elevator.move() | 1 | 4 | 6 |
Elevator.opendoor() | 1 | 3 | 3 |
Elevator.pop() | 1 | 2 | 2 |
Elevator.push() | 3 | 5 | 8 |
Elevator.run() | 3 | 9 | 9 |
Elevator.upordown() | 13 | 10 | 16 |
Input.run() | 3 | 5 | 5 |
Isfunc.Isfunc() | 1 | 1 | 1 |
Isfunc.getIsfunc() | 1 | 1 | 1 |
Isfunc.notFunc() | 1 | 1 | 1 |
Total | 34.0 | 51.0 | 64.0 |
Average | 2.27 | 3.4 | 4.27 |
class | OCavg | WMC |
---|---|---|
Input | 2.0 | 4.0 |
Elevator | 3.89 | 35 |
Isfunc | 1.0 | 3.0 |
Main | 2.0 | 2.0 |
Total | 44.0 | |
Average | 2.93 | 11.0 |
电梯类的复杂度过高,主要是因为没有调度器的存在,电梯行使了这部分功能。
1.2、多电梯可捎带调度方案
和上次相比基本没有改动,只是在Main类里面,新增五个电梯,根据输入进行选择开始线程。和上次电梯相比,这次电梯多了载重,所以为电梯增加最大人数的属性,当人数最大,就不再从等待队列中获取人。没有设置调度器来协调多部电梯的工作,采用了“无为而治”的做法,让多个电梯线程自己抢人,程序只需要维护好线程安全,这次作业可以说是目前我完成的最轻松的一次作业了,特别是和第一单元次次重构相比。
UML类图
也是同上次,没有任何区别
时序图
时序图也是基本没有区别
复杂度分析:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.closedoor() | 1.0 | 3.0 | 3.0 |
Elevator.empty(HashMap) | 2.0 | 2.0 | 4.0 |
Elevator.move() | 1.0 | 4.0 | 8.0 |
Elevator.opendoor() | 1.0 | 3.0 | 3.0 |
Elevator.pop() | 1.0 | 2.0 | 2.0 |
Elevator.push() | 4.0 | 7.0 | 10.0 |
Elevator.run() | 3.0 | 9.0 | 9.0 |
Elevator.upordown() | 13.0 | 10.0 | 16.0 |
Input.getNum() | 1.0 | 1.0 | 1.0 |
Input.run() | 3.0 | 5.0 | 5.0 |
Isfunc.getIsfunc() | 1.0 | 1.0 | 1.0 |
Isfunc.Isfunc() | 1.0 | 1.0 | 1.0 |
Isfunc.notFunc() | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 2.0 | 3.0 | 8.0 |
Total | 37.0 | 55.0 | 75.0 |
Average | 2.3125 | 3.4375 | 4.6875 |
class | OCavg | WMC |
---|---|---|
Elevator | 4.22 | 38.0 |
Input | 1.66 | 5.0 |
Isfunc | 1.0 | 3.0 |
Main | 8.0 | 8.0 |
Total | 54.0 | |
Average | 3.375 | 13.5 |
基本与上一次一致,因为Main需要判断启动多少个电梯线程,所以复杂度增加。
1.3、多电梯协同调度方案
电梯的动态增加,只需要在Input类里根据输入启动新的电梯线程就好,但是多种类型的电梯换乘迫使我写了调度器。输入类与调度器构成单生产者-单消费者,共同管理等待队列。调度器分别和不同类型的电梯管理各自的等待队列,构成单生产者-多消费者模型。调度器实现的换乘方案是,若可到达,在目的地下电梯,若不可到达在最近的楼层,下电梯,等待换乘。同时,这个人会被丢回输入类和调度器共同管理的等待队列中。这种换乘需要特殊考虑一下3楼的换乘情况。
多个类型的电梯统一由电梯类构造即可,只需要在构造里面更改一下,根据类型来决定属性即可。
此次判断电梯的结束需要满足更多的条件,是否有人没有换乘完毕,所以新增了对象reqnum,来记录所有乘客的情况,乘客进去最外部等待队列时,reqnum+1,乘客在目的地出电梯时,reqnum-1,最终所有请求全部处理完毕,才可结束线程。这个记录请求的类为了保证安全,方法全部使用synchronized加锁。
指导书中提示,输入输出类也需要进行线程安全的保护,感谢热心同学们在讨论区发布的,封装方法,使用加锁的输出方法即可。
一开始,考虑利用电梯数量,进行优化,发现基本没什么用,而且会因为利用电梯数量的排序,导致工作量全部集中在某一类电梯上,后来看了学长的博客,采用了一种随机均摊方法,效果还比较好,不过听说这次“无为而治”的同学,性能分很不错。随机均摊,就是利用随机数分配,把人随机分配给某个类电梯的等待队列。
这次设计的可以说是可扩展性不好,觉得如果有第四次作业可能要更改很多。
UML类图
时序图
复杂度分析:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Assign.assignfloor(PersonRequest,int) | 3.0 | 4.0 | 4.0 |
Assign.empty(HashMap>) | 2.0 | 2.0 | 4.0 |
Assign.midfloor(int,int,int) | 13.0 | 5.0 | 15.0 |
Assign.numqueue() | 1.0 | 1.0 | 1.0 |
Assign.run() | 3.0 | 11.0 | 12.0 |
Elevator.closedoor() | 1.0 | 3.0 | 3.0 |
Elevator.empty(HashMap>) | 3.0 | 3.0 | 4.0 |
Elevator.midfloor(int,int) | 13.0 | 7.0 | 15.0 |
Elevator.move() | 1.0 | 4.0 | 8.0 |
Elevator.opendoor() | 1.0 | 3.0 | 3.0 |
Elevator.pop() | 1.0 | 4.0 | 4.0 |
Elevator.push() | 4.0 | 7.0 | 10.0 |
Elevator.run() | 3.0 | 12.0 | 12.0 |
Elevator.upordown() | 13.0 | 13.0 | 19.0 |
Input.gettype(ElevatorRequest) | 3.0 | 2.0 | 3.0 |
Input.run() | 3.0 | 7.0 | 7.0 |
Isfunc.getIsfunc() | 1.0 | 1.0 | 1.0 |
Isfunc.Isfunc() | 1.0 | 1.0 | 1. |
Isfunc.notFunc() | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 6.0 | 6. |
Reqnum.addReqnum() | 1.0 | 1.0 | 1.0 |
Reqnum.getReqnum() | 1.0 | 1.0 | 1. |
Reqnum.minReqnum() | 1.0 | 1.0 | 1.0 |
Reqnum.Reqnum() | 1.0 | 1.0 | 1.0 |
Safeout.println(String) | 1.0 | 1.0 | 1.0 |
Total | 81.0 | 110.0 | 149.0 |
Average | 2.89 | 3.92 | 5.32 |
class | OCavg | WMC |
---|---|---|
Assign | 4.67 | 28.0 |
Elevator | 5.9 | 59.0 |
Input | 3.0 | 9.0 |
Isfunc | 1.0 | 3.0 |
Main | 6.0 | 6.0 |
Reqnum | 1.0 | 4.0 |
Safeout | 1.0 | 1.0 |
Total | 110.0 | |
Average | 3.93 | 15.71 |
换乘楼层的确定复杂度较高,多个类可能由于共享对象的增多导致了复杂度较高。
SOLID原则
1、单一责任:每个类只管自己该管的事情。电梯运输乘客,调度器才负责具体哪个乘客上哪个电梯。
2、开闭控制:哪些类支持扩展,哪些类是final的,哪些函数设置为对外接口,哪些函数是封装的。这次只有在输出进行了加锁封装,其他地方没有体现。
3、里氏替换:和自定义类的继承(is-a继承)有关。没有实现继承,目前也不需要。
4、依赖倒置:强调依赖关系中,高层模块的抽象。本次作业中没有体现,在第一单元中有所体现。
5、接口分离:不要用单一接口实现多功能。本次作业中没有体现
2、bug分析
三次作业强测互测都没出现问题,可能是因为我三次都在和平屋,屋里大家全都热爱和平。
第六次弱测出现的主要问题是输入的缓冲,如果程序中有两个输入流可能导致前面几个人被吞,解决方法在main函数为了读取电梯数目,已经有了一个elevatorInput的对象,把这个对象传递给电梯输入类。感谢同学在讨论区的分享。
第七次弱测出现了wa和rtle的情况,wa主要是忽略了还有人换乘程序就结束了,解决方案是创建一个reqnum类,有人加入等待队列就+1,有人到达目的地就-1,最后reqnum==0和之前的条件一起结束线程,为了保证线程安全,这个类所有方法都加锁实现同步。rtle的情况,通过JProfiler查看,有线程没有结束一直在阻塞状态。使print大法,看看哪个线程没有结束。 结果是当第一个电梯线程结束时,没有进行其他线程的唤起,导致其他阻塞线程一直在阻塞,加了一个 condition.signalAll()就解决了。
关于测试工具,前两次写了评测机,但没测出什么,可能是写的太垃圾了。
关于优化,目前来看在维护好线程安全的情况下,无为而治或许是很好的优化。没有想到其他好的优化方案,期待大佬们的分享。
3、收获
第一次学习了多线程,个人觉得ReentrantLock比synchronized好用。
关于线程控制,好像没有传说中那么可怕,只要弄清楚线程什么时候wait,什么时候结束,是否要唤醒其他线程就OK。但是目前对于多线程的掌握也只是一点点。
但是关于多线程的Debug真的是很难,目前只能依靠print大法以及自我分析,非常感想讨论区同学的分享,否则作业完成起来会更加困难。
二个单元结束了,第一单元主要形成了面向对象的思想,第二单元教会了我多线程,算的上收获满满。
希望接下来两个单元可以友善一些。