BUAA OO 第二单元总结

第二单元总结博客

 

第一次作业

第一次作业的基本目标是模拟单部多线程电梯的运行。

本次作业共设计了5个类,包括:输入处理类InputThread,调度类Schedule,等待队列类WaitQueue,电梯类Elevator。

1.同步块的设置和锁的选择

第一单元的锁比较简单:对InputThread,Schedule,Elevator这3个类加锁,且共享资源只有存放未入电梯的PersonRequests的waitQueue。

在InputThread中,当获取到新的PersonRequest后,申请waitQueue资源的使用并加锁,向waitQueue中添加对象后使用notifyAll唤醒其他进程。

本次作业中,Schedule类只进行判断进程的结束,这时要判断waitQueue是否为空,因此需要对这一行为加锁。

在Elevator类中实现了相应的调度策略,需要给电梯类中对waitQueue的删除操作加锁。

2.调度器设计(根据不同模式划分)

random:在电梯类中random()方法中实现

当电梯为空时,获取waitQueue中最早的请求设为主目标mainReq;若电梯非空,选择电梯中最早的请求作为主目标。在设置完主目标之后,根据主目标是否在电梯类来设置目的楼层target。在向目的楼层的运行过程中,若当前楼层的请求满足方向一致且电梯非空,则对该请求进行捎带。

morning:与random方法类似

Night:在电梯类中nIght()方法中实现

Night模式下的捎带策略和random模式相同,只是主请求的选择不同:当电梯在一楼时,选择最高楼层的人作为主请求。

3.功能设计和性能设计

UML类图

时序图:InputThread读入请求传给waitQueue,电梯从waitQueue中获取请求,InputThread控制Schedule的结束,由Schedule控制电梯的结束。

类复杂度:

依赖性复杂度:

行数统计:

本次作业中功能大部分集中在Elevator类中,因此电梯的行数较多且复杂度较高(见下图)。

虽然对电梯运行各阶段进行了封装,但在night和random方法中为了实现电梯的功能调用了大量函数导致复杂度过高。

其他的类都进行了良好的封装,行数、依赖性和复杂度相对都不高。

本次作业在电梯里设置了两个队列,分别存放电梯内的请求和分配给电梯的请求;设置的调度类在本次并未使用,而是为之后需要为电梯分配做准备,这两点都方便了之后的扩展。

4.分析自己程序的bug

本次作业在强测中出现了bug,出现在Elevator类的random方法中,错误是在接主请求时没有判断该请求是否在电梯队列中导致重复进入。

未出现线程安全相关的问题。

5.发现他人的bug

未能成功hack他人。

第二次作业

第二次作业要求模拟多部同型号电梯的运行,并要求能够响应输入的请求动态增加电梯。

本次作业整体架构和第一次作业相近,5个类包括:输入处理类InputThread,调度类Dispatch,等待队列类WaitQueue,电梯类Elevator。

1.同步块的设置和锁的选择

本次作业在同步块和锁的改动上主要体现在InputThread和Dispatch类中。

在InputThread中,为了实现响应请求增加电梯,增加了对电梯已分配请求队列的锁(之所以不增加电梯队列的锁,是因为对电梯的操作主要通过电梯已分配队列实现)。

在Dispatch中,为了实现乘客请求的分配,增加了对电梯已分配队列的锁。

2.调度器设计(分配策略)

调度器的作用是将InputThread传入的请求分配给电梯。

本次作业中,我采用的方法是依次分配,调度器内置一计数器,初始化为0,每分配一次后进行加一模电梯数的操作,将总请求队列中的请求循环发送给各个电梯。

3.功能设计和性能设计

UML类图:总体上架构不变

时序图:InputThread读入请求传给requestQueue,InputThread还可以根据请求直接创建新的电梯线程,Dispatch将requestQueue中的请求分配给电梯各自的waitQueue,电梯从waitQueue中获取请求,InputThread控制Dispatch的结束,由Dispatch控制电梯的结束。

类复杂度

依赖性复杂度

行数统计

由上图可知,本次作业依赖性复杂度低,问题仍在于电梯类过多的行数和较高的复杂度,此外,由于作为InputThread和Elevator交互的“托盘”,Dispatch类(即为第一次作业中的Schedule)的复杂度有所增加。

在本次作业中并未对可扩展性进行更深入的发掘,仅对Dispatch做了些许优化。

4.分析自己程序的bug

本次作业在强测和互测中出现了bug。

互测bug:Elevator的Night和Random中,当人数过多时,会发生主请求未进入电梯时,电梯已满的情况,这是主请求再进入会导致电梯超载。

强测bug:RTLE,由于每次只读入一个请求,会导致大量请求同时输入时,电梯只能接受少数几个,在电梯进人后增加一个wait(10)解决该问题。

5.发现他人的bug

未能成功hack他人。

第三次作业

第三次作业要求模拟多部不同型号电梯的运行。型号不同,指的是开关门速度,移动速度,限载人数,以及可停靠楼层的不同。

本次作业整体架构和第二次作业相近,5个类包括:输入处理类InputThread,调度类Dispatch,等待队列类WaitQueue,电梯类Elevator。

1.同步块的设置和锁的选择

本次作业的锁的设置没有改动。

2.调度器设计(分配策略)

本次作业中,我采用的方法是依次分配,调度器内置一计数器,初始化为0,每分配一次后进行加一模电梯数的操作,如果当前电梯种类满足请求的类型则存入,否则继续加一模电梯数,直至类型匹配,将总请求队列中的请求循环发送给各个电梯。

3.功能设计和性能设计

UML类图:总体上架构不变,电梯类中新增了几个字段用于标识类型

时序图:与第二次作业相同。

类复杂度

依赖性复杂度

行数统计

本次作业依赖性复杂度低,由于直接扩展自第二次作业,Elevator和Dispatch的问题仍然存在。 此外,由于循环new操作且多次公用对象,主类的循环复杂度较大。

4.分析自己程序的bug

未发现bug

5.发现他人的bug

未能成功hack他人。

心得体会

线程安全:

多线程开启之后,debug就变得更加困难,因为很多错误是不可复现的,且评测机和本地不同环境下运行的结果也不相同。实际上,我在第一次作业中花费了近20个小时,主要工作都在检查可能出现的死锁以及多线程并发出现的错误。三次作业之后,我的经验教训是要先设定好共享对象和加锁情景再进行程序的编写,这样有助于减少锁的个数,避免锁嵌套的出现(这有可能导致互锁)。此外,由于第一次作业中一直轮询导致我一直出错,之后加了wait()解决了很多问题。(这锅主要是因为我在写作业的时候没有注意到助教在讨论区有提示要避免一直轮询

层次化设计:

本次我认为我的层次化设计做的不错,除了第一次耗费了大量的精力理解多线程加各种重构,之后的两次作业都是建立在前一次作业的基础上很轻松就完成了(并没有太追求性能分)。每个类之间分工明确,没有说太杂糅的情况。

这次虽然过了3次作业,但是老师在课堂上讲授的设计模式我仅是借用了思想,没有做到具体的实现,在设计模式这方面还需要学习很多。

posted @ 2021-04-23 21:50  RANKA  阅读(57)  评论(0编辑  收藏  举报