OO第二单元设计——多线程电梯

第一次作业

据不可信消息,听说往届电梯系列第二次作业直接成为了我们的第一次作业。。。其实第一次也不太难,主要是自己还没为多线程的到来做好准备。

周二晚上发布的作业,当天(大概)看了一些廖雪峰老师的教程,周三看了一点《图解Java多线程设计模式》(不得不说这真是本好书,对我这样基础薄弱的菜鸡还是比较友好的)。

设计思路

在主线程main里进行各个对象的初始化、线程的开始、请求的读入与线程的终止,这也为第三次作业留下了隐患;

封装Elevator类并作为电梯线程;

封装RequestQueue类来维护请求队列并作为调度器;

封装Floor类为每一层保存等待队列、到达队列等。

调度算法

对于“不要重复造轮子”这句话应用在电梯设计中,可以理解成:现在电梯已经大规模投入使用很长时间了,除了特殊需求,日常生活中所使用的的电梯调度算法应该就是性能接近最好的算法。所以除了了解这些近似于磁盘调度的电梯调度算法,结合生活实际可能也是不错的角度。

了解了一些现有的电梯调度策略——LOOK, SCAN等,因为个人感觉当电梯空闲时,停留在中间楼层与上下往返运行对于随机请求可能响应的性能差不多,便设计成没有请求时停在中间楼层(8层)。对于运行时的捎带规则则基本遵循ALS规则。

除了这些,个人还加入了比较巧妙(投机取巧)的一个设计——在电梯上行和下行时进行“延迟判断”——所谓“延迟判断”,就是比如当电梯要从2楼向3楼运行,在完成2楼的上下乘客并关门后,可以先sleep(100),接着我们再判断是否还有乘客要上电梯,如果有就重新开门,如果没有就再sleep(300),然后到达3楼。这样可以节省电梯要重新到2楼接乘客的时间再上楼的时间(如果只有这一批乘客,且目标楼层的方向与运行方向一致),而在第三次作业中,对于上下一层需要600ms的C类电梯,由于上下楼层的用时增加了一半,所以“延迟判断”可以带来更大的回报。

结构分析

 

Elevator中的run()方法以及RequestQueue中的arrive()方法包含的情形以及执行的操作太多;主类Main略有臃肿。。。

应当将功能做更精细与专一的划分。

bug分析

由于这次作业的功能要求较少,所以确实不容易出bug,在强测与互测中都没有测出bug。

小结

除了一上来就得多线程之外,这次作业整体还是比较和谐的。

第二次作业

这次作业在第一次作业的基础上增加了楼层(除了要额外判断0层之外,基本没什么影响)和多部电梯。

结构分析

为了方便多个电梯的协调,给Floor类赋予了更多的属性与功能,其他基本沿用了第一次作业(说明第一次作业的可扩展性还Okk)。

这里给Floor一个特写。。。

 

 在设计时本想给每个电梯都安排一个调度器,但觉得每个调度器不仅要和电梯交互,还要和总调度器交互,耦合性可能会比较强,便还是在一个调度器的基础上继续扩展,这也导致了原本臃肿繁杂的对象、方法,现在变得更臃肿繁杂。

 

bug分析

这次作业因为“延迟判断”时给一种情形少sleep了300ms,导致错了两个点,但整体性能还不错。

幸免于互测。

交了两个数据,很愧疚地hack到了几位同学,发现大家最容易出错的地方可能还是在死锁——有的同学会一个电梯一个电梯地关闭(这在第三次作业中会非常容易吃亏),有的同学过于泛滥地使用lock或synchronized。

小结

大概有了多个电梯的项目才更能算得上是多线程,debug的过程也是比较麻烦,特别是bug复现。

在这次作业中,还听到有同学已经使用了换乘的方法,感觉这种方法可能会在互测中被针对,便没有过多考虑了,不过也没在自己的room里碰到这位同学。

第三次作业

这次作业的复杂度相较于第二次作业有了很大的提升——电梯种类不同,可中途加入电梯,电梯停靠楼层有限制——这就不得不引入换乘。

设计思路

了解了一下同学们的方法,主要分为两类——一类是在读入请求时就为请求设计好“路线”的静态方法,这种方法方便后期调度器的分配,根据大家的反应,似乎性能也不错;另一类是每个请求在进入电梯后根据自己所乘的电梯来设计“路线”,这种方法可能动态适应性不错,但调度器的工作量就得增大不少。

我在这里采用了后者。事实上,对于主请求而言,分配到的电梯也还是根据自己的算法——结合出发楼层、目标楼层、换乘需求等——来完成的。

结构分析

相较于第二次作业,这次作业有了较大的改动:

从主线程main里分理出读入请求与终止线程的线程Input;

将课程组提供的PersonRequest扩展成Myrequest,以应对换乘的需求;

从Elevator中继承出三类电梯;

调度器RequestQueue中增加、修改换乘的一些列判断、执行方法。

 

 由于每类电梯能停靠的楼层都不同,所以需要对从Elevator中继承下来的方法进行覆写(现在来看,有的方法也可以不去覆写)

(RequestQueue中的arrive()方法当然还是一样的臃肿。。。) 

bug分析

我也不知道为什么一到第三次作业就会有所松懈,于是这次又没有进行较多的测试。

事实上在中测时就发现最后一个点会因为sleep的时间不够而出现CPU_TLE,当时大概也知道这是因为在判定结束时没有用wait()而用了sleep(),但当时就是懒了一下,不太愿意再开一个线程,只是修改了一下sleep()的时间。结果很多点出现了CPU_TLE(甚至当时居然会有预感)。。。

小结&第二单元惨痛教训

虽然第三次作业比较惨痛,但其扩展性应该还是不错的。比如如果需要中途退出某一部或几部电梯,由于电梯间的耦合性很小,所以应该之需要稍微调整就可以直接沿用而不会对其他线程产生影响。

我们的电梯系列作业不能单纯地使用生产者-消费者模式,也不能直接套用工人线程模式。虽然大体上可以认为输入线程提(生产者)供请求到请求队列和调度器中,电梯线程(消费者)取出请求,完成运送,但传统的这些模式都是一个线程一次只处理一个请求,而我们的电梯需要进行捎带,即同时处理多个请求(否则性能肯定非常差,而且也不符合生活实际)。而电梯的捎带策略必然给电梯与调度器之间增加了耦合度,如何维护请求等待队列与电梯携带队列是作业的关键。

下个单元做第三次作业时一定强迫自己多进行测试,不能再踏入同一条河流了。。。

猜你喜欢

转载自www.cnblogs.com/kjs001019/p/12716007.html