OO第二单元总结——可捎带电梯

OO第二单元总结——电梯

1、设计分析

1.1、单电梯可捎带调度方案

第一次接触多线程,单部电梯采用了单生产者—单消费者的模式,输入线程作为生产者,电梯作为消费者,二者共享一个排队队列hashmap<integer,linkedblockingqueue>记录电梯外等待的人,以及出发楼层,之所以选择hashmap这个容器,是为了实现可捎带的要求。电梯内部设置一个队列,自我管理,记录已经上电梯的人,以及目的地。通过ReentrantLock,Condition实现线程的阻塞,唤起。生产者、消费者共享一个对象,用来结束所有线程。电梯的调度算法采用了LOOK算法。

这次个人觉得可扩展性良好,符合设计模型。

不足之处,电梯内部略复杂。

UML类图

OO第二单元总结——可捎带电梯

时序图

OO第二单元总结——可捎带电梯

复杂度分析:

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类图

OO第二单元总结——可捎带电梯

也是同上次,没有任何区别

时序图

OO第二单元总结——可捎带电梯

时序图也是基本没有区别

复杂度分析:

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类图

OO第二单元总结——可捎带电梯

时序图

OO第二单元总结——可捎带电梯

复杂度分析:

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大法以及自我分析,非常感想讨论区同学的分享,否则作业完成起来会更加困难。

二个单元结束了,第一单元主要形成了面向对象的思想,第二单元教会了我多线程,算的上收获满满。

希望接下来两个单元可以友善一些。

上一篇:OO第二单元--电梯调度


下一篇:BUAA_OO(2020)_Unit2_Summary