BUAA OO第二单元总结:电梯
oo第二单元来到了久闻大名的多线程电梯模块,在这个部分,我们需要学习用多线程的手段来完成电梯的实时性运行、调度、及最终的终止工作。总的来说,除了第一次作业学习和接触多线程,花费了非常多的时间,后面的几次作业相对来说,由于没有了那种痛苦的重构过程,所以极大地减少了工作量,也让我有很多的时间去思考多线程的运行。
一、三次作业架构基本分析
第一次作业
1.总体架构
第一次作业要求完成一个多线程的单部电梯,需要满足ALS的捎带策略。整个设计满足生产者--消费者模式,生产者为输入线程(InputThread类),消费者为电梯线程(Elevator类),托盘为WaitQueue类,Main类为完成主线程的创建,还有一个Perrequest类来代表每个请求对应的特点。
基本的运行流程是电梯请求通过输入线程InputThread放入WaitQueue中,Elevator线程从中不断取出请求,并进行对应的处理,而在电梯运行的过程中会完成捎带的请求。其中比较重要的共享对象就是WaitQueue。
2.同步块设置和锁的设置
在极为重要的共享对象类WaitQueue中,我使用了ArrayBlockingQueue这样一个线程安全的数据类型来存储请求,这在很大程度上减少了同步块和锁的设置,由于对于等待请求的存取将阻塞地进行,因此减少了工作量。
当然为了确保线程的安全,所有关于读写WaitQueue的对应代码块(例如处理捎带,取出请求和放入请求等),都使用了synchronized的同步锁来确保读写的相对独立,这样只有在线程取得对应锁的时候,才能进行相应的操作。
3.调度器设计
由于在本次作业中没有设计到多个电梯的交互,因此并没有安排调度器来完成任务的分派和进行,即是通过电梯线程自己的取用和分析来完成调度工作。
4.调度策略安排
在本次作业中,完成了基本的ALS捎带工作,即是首先电梯线程处理的请求包括主请求和捎带请求,每次电梯运行会首先取一个主请求,并在完成主请求的过程中,通过判断电梯上下行方向和捎带请求是否符合,以及电梯的载人余额来实现捎带工作。在一次处理主请求的过程中,会直到主请求处理完并且电梯为空载的时候才会退出本次请求处理,并会在下一次循环时判断是否结束线程还是继续处理下一步的主请求。
第二次作业
1.总体架构
第二次作业要求完成一个多线程的多部电梯,相比第一次作业,主要是增加了两部电梯,并以自由竞争的模式让电梯竞争请求队列,其他并无较大变化。
2.同步块设置和锁的设置
同样是由于使用了ArrayBlockingQueue这样一个线程安全的数据类型来存储请求,从而在多电梯共同取请求的时候,可以实现阻塞安全的取用,减少了同步设锁的工作量。
3.调度器设计
本次作业中设计到多个电梯的交互,但是由于最终我使用了自由竞争等待队列的方式来实现多电梯的交互,因此实际上并没有设立调度器来完成本次作业。
第三次作业
1.总体架构
第三次作业要求完成一个多线程的多部不同型号电梯,相比第二次作业,主要是针对不同的电梯,设置了不同的等待队列,在读入请求时,将会根据请求的特点发放至对应的等待队列中,由相应的电梯来完成工作。
2.同步块设置和锁的设置
与第二次相同。
3.调度器设计
在本次作业我在输入线程中设置了一个简单的调度器,而没有专门设置一个调度器线程,我首先为三类电梯分别安排了三个WaitQueue,然后如果电梯起始和终止位置在1-3层或者17-20层,将会优先将其分给C电梯对应的等待队列,而后如果电梯起始和终止位置都在奇数层,将会优先将其分给B电梯对应的等待队列,如果都不符合,则会分给A电梯,当然这种调度的方式有其自身很大的局限性,因为没有设置换乘的工作,导致对于一些特殊的样例,电梯运行的效率将会比较低。
二、UML分析
UML类图

UML时序图

可扩展性分析
功能设计
第三次作业最终实现的是自由竞争式的多线程电梯,对于增加新的电梯请求,满足新的要求,可能需要增加新的等待队列和对应的分配规则,但是如果只是增加同型号电梯,则无需扩展,相对来说扩展性较好。
性能设计
由于该次作业尚未实现电梯的换乘,因此在某些样例上可能会导致电梯性能偏低,增加新的电梯型号,可能同样会面对类似的问题,所以扩展性不够,还有较大的提升空间,可能在实现换乘之后,会较大程度上提高其性能扩展性。
三、BUG分析
第一次作业
本次互测和强测均未出现Bug。
第二次作业
本次作业中强测出现了一个BUG:REAL_TIME_LIMIT_EXCEED。
经过代码的静态分析和实际的测试之后发现,是由于多部电梯在竞争同一个队列的时候,当读入一个电梯请求的时候,使用的是notifyall(),而这会唤醒所有的电梯,由于三个电梯被唤醒,但是只有一个请求,所以会有两个电梯会读到Null,导致这两个电梯直接结束了线程,最终导致一个电梯运行而超时,而其实解决方法也非常简单,只需要将其改成norify()即可。
第三次作业
本次作业中强测未出现bug,互测部分出现了2个Bug,第一个Bug问题出现在当加电梯的请求和结束输入同时输入的时候,会使得新加电梯的线程不能正确读入到结束输入产生的notify(),导致其一直wait,无法再次被唤醒,从而使线程无法正常结束。
另一个Bug是由于未引入换乘机制,导致构造的边界样例会出现超时的错误。
四、HACK方法
本单元由于多线程的hack特点比较迷,对于同样的样例可能需要多次执行,和构造大量样例才能实现,因此本次hack的成功率偏低,也没有进行太多的尝试。
五、心得体会
1.线程安全
在线程安全方面,其实是需要花比较多功夫去思考和摸索的,从开始接触多线程到第三次作业告终,整个过程中由于线程不安全出现的锅没少碰到过,对于线程不安全的情况,不同线程的执行策略就可能直接导致错误的发生。其实回过头来看抓住关键的共享对象,合理的在同步区上锁其实是最核心的控制线程安全的操作,虽然实际执行起来,可能会碰到各种各样的bug,但是万变不离其宗,归根结底完成线程运行的分析,合理的上锁,及时进行wait和notify,可能才是多线程实现线程安全的核心。
2.层次化设计
我觉得层次化设计主要是实现各线程,各类之间的交互,最终构建一个较为合理的架构,在每个类中需要实现各类最核心的函数内容,而将其他耦合性偏低的工作可以交给其他类来完成,例如电梯策略类其实是可以单独设一个类来实现的,对于整体的架构其实第一次上级的代码给了较多的启迪,后续整个代码的实现,和层次化设计也基本上是在该实验代码的基础上完成。
浙公网安备 33010602011771号