BUAA-OO-Unit2 多线程历险

Unit2 多线程电梯历险

第五次作业

第五次作业采用的是producer-customer模式,InputThread线程作为生产者获得输入后将输入请求放入电梯对应的托盘RequestQueue中,Elevator线程作为消费者从RequestQueue中获得请求后对请求进行处理。

同步块设置及锁的选择

同步块是用来对有多个线程共享的资源进行同步的,避免线程竞争造成线程不安全。在我的程序中,主要是对RequestQueue中的putRequest(),getRequest(),isEnd()和setEnd()这几个方法用synchornized对方法进行加锁。

调度器设计与电梯运行策略

这次作业我并未采用调度器,直接由输入线程将每个电梯的请求分配到每个电梯对应的RequestQueue的。

电梯的运行策略采用的是look策略的变式,当电梯内有乘客时以电梯内的乘客为主请求,当电梯内没有乘客时检查运行同方向是否有请求,同方向有请求时就运行到同方向的最远端的请求所在楼层,没有请求时就转向。电梯每到一个楼层会在不超载的前提下接该楼层的所有人,然后优先运送与电梯方向相同的乘客,电梯反向后在运送另一个方向的乘客。

UML类图与UML协作图

Bug分析及Hack策略

这次的程序有一个地方把对象的名字写错了,本来应该判断电梯里面乘客队列outRequest的大小不超过6,我没注意写成了准备出电梯的乘客的队列out,结果就是只要是等待队列中有超过6个人的情况就会WA。还有一个问题是我的程序中没有对输出进行加锁,导致输出的时间戳不是严格递增的。

由于身处C房,根本就不需要Hack策略,基本随便一个数据就能测出好几个bug。

第六次作业

第六次作业增加了横向电梯,同时中途也可以增加电梯了,不过,之前的Producer-Customer设计模式还是能够适用的,只不过要增加一级RequestQueue和调度器。InputThread负责获得输入请求和产生电梯,Scheduler线程负责分配请求,Elevator线程负责处理请求,RequestQueue充当托盘暂存请求。

调度器设计与电梯运行策略

调度器的职责是获得电梯状态并根据电梯所在楼层、运行方向、电梯人数计算出当前请求的合适电梯,然后把该请求放入电梯对应的RequestQueue中,不过计算的结果并不是很好,和自由竞争相比还是差了一点的,感觉还不如自由竞争的策略,既不用考虑调度策略,还可以有比较高的性能。

对于横向电梯,我并没有采用上述调度器,而是采用的自由竞争的策略,把所有得对应楼座的请求放入对应的楼座RequestQueue中,哪个电梯先到由哪个电梯处理请求。纵向电梯是采用的和第五次作业人相同的策略。

UML类图和UML协作图

Bug分析及Hack策略

这次作业吸取了上次作业的教训,在我自写的测试程序的加持下并未出现bug,还算有不错的效果。测试程序使用的是随机数据,附带结果检查程序,只是只能检查结果的正确性和线程会不会结束,并不能对RTLE和CTLE进行测试。同时我还针对性的构造了一些数据用来对我的程序进行测试。

hack我也是使用的我的测试程序,不过并没有发现bug。

第七次作业

第七次作业相比于第六次作业增加了换乘需求,同时也增加了电梯的客制化。所以整体的框架并没有较大的改变,对于我的作业只是增加了一个Passenger类用来管理乘客的请求。当InputThread获得输入请求时,会把请求包装成Passenger并放到waitingRequest中,scheduler从waitingRequest获得该Passenger后调用Passenger的generatePathfangfa将Passenger的请求分解为多个请求,即生成该乘客的路径。

调度器策略与电梯运行策略

这次作业横向电梯和纵向电梯都是采用的自由竞争的策略,毕竟简单又好用。调度器的主要职责时获得请求,并根据电梯的速度、容量等信息生成乘客的路径后把乘客放到对应的电梯中。

横向电梯和纵向电梯的运行策略和第六次相同。

UML类图和UML协作图

Bug分析及Hack策略

这次作业的测试程序的弊端就凸显出来了,由于没办法测试CPU的占用时间,所以这次程序中出现了CTLE的错误。虽然使用了linux的time指令,但只针对部分数据进行了测试并未发现超时问题。错误的主要原因是对wait()和notifyall()的理解不到位,导致RequestQueue中的对部分方法对notifyall()的使用不合理,导致两个电梯互相唤醒,占用了大量CPU资源而超时。

Hack策略还是使用我的评测程序对房间的代码进行测试,同时构造了一些极端的数据进行测试。

心得体会

正如标题,这次的多线程真的像是在历险一样,路上有线程不安全、代码块同步、CPU轮询等敌人需要攻克,你必须要武装好自己的代码抵御他们的进攻,一不小心就会被他们趁虚而入,甚至自己被打了一下都不知道敌人是谁、在哪打得,结果就是自己的分数尽失。

多线程很玄学,就算是同一个数据,执行的顺序不同也会导致不同的结果,往往是找到一个bug后需要测试很多次才能定位到bug的所在地,然后在花费很长时间去发现bug产生的原因。这时候我觉得利用printf()进行调试可能要比用IDEA进行调试还要快很多。

虽然多线程历险有很多挫折和困难,不过通过对多线程的学习,我从对多线程一点不了解到现在有了初步的见解,并对简单的互斥和同步控制有了一些掌握。在多线程中,一般都是使用共享对象来实现资源的传递与共享,极力避免直接用一个线程去访问另一个线程的资源。而在资源的传递与共享时如何使用好对象锁以保证线程的同步便是关键,同时再配合合理的wait()和notifyall()方法来避免CPU轮询来高效利用CPU。

posted @ 2022-04-28 16:53  Xbuluo  阅读(22)  评论(1编辑  收藏  举报