BUAA_OO 第二单元总结博客

BUAA_OO 第二单元总结博客

设计思路

第一次作业

类图(ps:相应类的说明见图中备注)

第二次作业

类图

sequence diagram
main函数的时序图

main函数创建了三个Elevator和一个getInput对象,并创建和开启了相应线程

getInput中的run()时序图

该线程使用ElevatorInput中的nextRequest函数获得了下一个请求,如果获得电梯请求,则新创建一个电梯对象使其开始运行,如果获得一个PersonRequest,则在获得请求后唤醒所有wait中的线程,让电梯执行该请求。

Elevator中的run()的时序图

电梯线程直接运行解决myQueue中的请求,当当前请求数量不足时,通过getEleRequest()方法从allQueue中获得新请求放入myQueue中。

第三次作业

类图

sequence diagram
main线程

初始化并创建各个线程

getInput()中的线程

获取输入的线程,在接收到电梯类request时根据种类创建电梯。

电梯线程(以B线程为例)

电梯线程主要用于处理request队列,并在合适的时候向allQueue中取出或者放入request。

可拓展性

在第三次作业中,由于采用集中式调度,所以可拓展性不强,如果电梯数量增加,都向allQueue对象请求锁时,会给其带来巨大压力,且如果电梯属性改变,需要直接对电梯的run方法进行修改,不能够沿用原有设计,且电梯线程之间相互唤醒可能会造成线程不安全的情况发生。

线程设计

第一次作业

同步块与锁的设置

在第一次作业中,由于只有一个电梯,我选择allQueue(即包含所有request的队列)对象使用synchronized关键字上锁,即保护request队列每次只被一个线程改变。在同步块的设置方面,我将整个电梯获得request,进行上下移动的过程作为同步块上锁。由于第一次作业只有一台电梯,所以对于整个电梯移动过程上锁对于性能并没有很大影响,但在本单元中后续作业中这样的方法造成CTLE的情况。

在同步块中,实际需要上锁的位置为电梯在运行到达相应request的fromfloor时,在allQueue中将该需求取出并放入电梯内部队列中,其他的语句实际并不需要读取或改变allQueue。

调度器的设置

在第一次作业中,由于只有一部电梯,我并没有设计调度器,而是直接将getInput()线程中取到的需求放入allQueue中,由电梯直接进行读取。

第二次作业

同步块与锁的设置

第二次作业与第一次作业一样,依旧是使用synchronized将allQueue对象上锁来保护request队列不同时被多个线程所改变,即每次只有一个线程放入或取出request。在同步块的设置方面,我将elevator中取request的部分以及getInput线程中放入request的部分作为同步块加锁,将电梯得到请求与电梯处理请求两个部分分开,避免一个线程一直持锁的情况。当电梯中myQueue队列需要request填充时,elevator线程获取锁并通过getEleRequest方法得到请求队列放入myQueue中,如果当前请求队列为空且没有收到end信号,则进行wait操作,直到新请求输入将其唤醒;如果当前请求队列为空、电梯中所有请求处理完毕且request输入结束,则结束电梯线程。

调度器的设置

在这次电梯中,我仍旧采用集中式调度,所有电梯共享一个request队列,自由竞争乘客需求,当当前电梯需要进行输入时,直接取得锁并通过allQueue中方法获取请求 ,在获取请求之后释放锁,执行changeFloor函数进行电梯运行。

第三次作业

同步块与锁的设置

在第三次作业中,仍旧使用synchronized关键字将allQueue队列作为上锁的对象,三种elevator根据自己的需求向getEleRequest方法中传入参数获得相应的请求队列,对于elevatorB而言,还需要再本电梯完成不了请求时重新创建一个PersonRequest并通过getBack方法传入allQueue中等待elevatorA处理。

调度器的设置

本次作业中,我依旧采用集中式调度,让各类电梯根据自己的需求自由竞争request,由于本次作业中电梯只有6个以下,所以为了竞争request而给allQueue上锁并不会对该对象造成巨大压力,但如果电梯数量增加,都需要获得allQueue对象的锁并获取request时这种方法效果会急剧下降。

Bug分析

第一次作业

自己程序的bug

在第一次作业中,我的bug主要来自于getInput()这一线程,在normal模式下,每次获取输入之后,该线程会进行wait操作,直到elevator线程将其唤醒,而在elevator线程中,当且仅当电梯中的所有人被运输出去的时候才会唤醒getInput()线程,获取输入,也就是说,在normal模式下,电梯运行的过程时每次只能够获得一个输入,没有进行捎带(捎带策略没有得到真正的执行,因为根本就没有获得request :(

这个bug在出现的主要原因是对于线程之间的关系不够理解,对于wait和notifyall机制仍旧十分模糊。

第二次作业

在本次作业中,我的bug来源于本来需要等待的线程由于执行notifyall而相互唤醒。在Elevator线程中,如果当前电梯中的所有需求都被执行完毕且当前请求队列allQueue为空时,该电梯会执行wait操作,但由于在完成本次作业时对线程机制不够了解,为了避免线程死锁,我将所有的wait前面都加了notifyall操作,因此,在allQueue长时间为空时,Elevator本该全部进入wait状态,等待输入新request时被唤醒,但由于Elevator前的notify all导致所有电梯相互唤醒,不能够进入wait状态,造成了错误。在删除了Elevator前的notifyall操作后,bug顺利修复。(所以RE不一定是抛了异常!!~_~)

第三次作业

在本次作业中,产生bug的原因是elevatorA先于elevatorB结束后由于elevatorB未能完成其中乘客请求输出新请求无人处理最终导致乘客需求未能完成。在elevatorA中,只需要判断当前电梯中request处理完毕、allQueue为空且外部请求输入完毕就可以结束线程,然而,如果此时elevatorB中请求还未运行完毕,有可能出现需要换乘的请求,当elevatorB将该请求输出后,由于elevatorA线程已经结束,没有电梯能够处理该请求,最终导致请求无法被处理,乘客无法到达相应楼层。

发现别人bug的策略

在测试别人代码的时候,我编写了一个程序能够自动模拟定时输入,运行别人的程序并观察时间即可判断该代码是否存在死锁问题,是否能够输出,是否超时等问题,并利用测试自己的程序时产生的bug来构造有针对性的测试样例。

心得体会

在这一单元的作业中,我学习了多线程编程相关知识,对于各个线程运行的机制有了更加深入的了解(任意两条语句都有可能连续执行,任何一种顺序都有可能!!!!(除非加锁))。在多线程运行时,有可能程序在大部分运行顺序下都能出现正确结果,但在某些运行顺序下会出现一些不可预知的错误(bug的最终来源\o\)。在进行本单元作业的过程中,我大部分的错误就来源于线程安全问题。

虽然本单元的作业依旧给我带来了不小的挑战,但体验感比上一单元还是好一点点(大概是因为基本没有进行重构叭),希望在接下来两个单元的学习中我能够继续提高!

posted @ 2021-04-25 16:08  ZIMUQIN  阅读(69)  评论(3编辑  收藏  举报