BUAA OO 2022 第二单元总结

BUAA OO 2022 第二单元总结

一、第一次作业

本次作业有且仅有五部纵向电梯,对应五个楼座。

1、同步块设置

第一次作业的同步块仅有两处,一个为每个楼座的等待队列,在输入请求时,对相应等待队列执行写操作,将请求加入该楼座的等待队列;在电梯运行开门需要上人时,访问等待队列取出请求。另一个为Output模块的输出方法,主要用于保证输出的时序性。

2、调度器设计

在Analyse进行输入读取,按照输入请求的出发楼座,调用对应电梯Elevator的添加请求方法,将请求加入对应楼座的等待队列Wait中。

在电梯Elevator运行开门需要上人时,访问等待队列Wait,并取出目的楼层和主请求或当前楼层(不存在主请求时)最近的请求加入电梯内队列,再重新将目的楼层最近的请求设为主请求。

3、架构模式

3.1 UML类图

MainClass模块负责创建并运行五个Elevator进程与一个Analyse进程。

Building模块负责装载集合Elevator电梯进程,并与Analyse模块连接以调用电梯模块的方法。

Analyse模块进行输入读取,按照输入请求的出发楼座,调用对应电梯Elevator的添加请求方法,将请求加入对应楼座的等待队列Wait中;在输入完毕后,进程结束,并向Elevator模块发送weak()唤醒wait状态,并传递工作结束信号。

Output模块负责使用互斥锁输出带有时间戳的字符串,以保证输出的时序性。

Wait模块对应每一个楼座的等待队列。

Elevator模块为整个程序最为关键的运行进程,负责使用first()方法从该楼座等待队列中寻找第一个乘客,若等待队列没有乘客则执行wait;找到第一个乘客后,向第一个乘客所在楼层或main请求的目的楼层上行或下行,每到达一个楼层时都会根据电梯内以及电梯外的请求队列判断是否开关门、上下;当接收到Analyse的工作结束信号后,等到等待队列与电梯内队列均为空时即结束进程。

3.2 复杂度分析

①类度量

②方法度量

Elevator模块作为电梯运行模块,包含主要的电梯上下行以及开关门策略方法;其他模块都负责一个具体功能,例如:输入、创建进程、输出、等待队列。代码整体内聚度较高,模块之间分工明确。

二、第二次作业

本次作业可以根据输入增添纵向电梯与横向电梯,跨楼座的请求保证出发楼层与目的楼层相同且所在楼层一定已添加横向电梯。

1、同步块设置

在第一次作业的基础上,本次的同步块仅添加了Waitnum模块,用于记录某一楼座或楼层等待队列的等待人数。每次等待队列人数变化时,对该同步块执行写操作;在使用first()方法时,访问该同步块获得等待队列人数。

2、调度器设计

在第一次的基础上,为每一层添加了横向等待队列,将横向请求直接加入对应楼层的横向等待队列中。

添加了横向电梯进程Crossing模块,相较于纵向电梯Elevator,将纵向逻辑更改为横向逻辑,并在模块内增加了根据楼座判断最近距离与方向的方法。

3、架构模式

3.1 UML类图

在第一次作业的基础上,增加了Waitnum模块用于专门记录对应的等待队列人数;增加了Crossing横向电梯模块,使用横向逻辑处理横向运行的请求。

3.2 复杂度分析

①类度量

②方法度量

将等待队列的人数单独列为一个Waitnum模块,内聚度进一步提高。

三、第三次作业

本次作业为每一个电梯增加了特有的做大人数限制与运行速度,横向电梯限制了停靠楼座,一楼初始即存在一个横向电梯,请求可以同时跨楼层与跨楼座。

1、同步块设置

无新增同步块。

2、调度器设计

Analyse模块输入请求时,判断开始楼座与目的楼座是否相同,若不同且起始楼层存在横向可到达起始楼座与目的楼座的电梯则将请求直接放入横向队列,否则放入纵向队列。

Elevator模块运行时,使用Target()方法确定请求的目的楼层,若目的楼座与电梯所在楼座不一致则目的楼层为最近的可到达请求的起始楼座与目的楼座的电梯所在楼层(使用Building模块的nearCos()方法获得),否则目的楼层为请求的目的楼层;下电梯时,若所在楼座与目的楼座不一致,则将该请求放入横向等待队列。

Crossing模块运行时,first()方法内使用waitnum()方法获得该楼层等待队列中起始楼座与目的楼座均符合该横向电梯停靠限制的请求数量以判断是否进入wait状态;下电梯时,若目的楼层与所在楼层不一致,则将该请求放入纵向等待队列。

3、架构模式

3.1 UML类图

3.2 复杂度分析

①类度量

②方法度量

本次作业主要针对横向电梯的停靠限制做了关于换乘的修改,增添了寻找最近横向电梯,请求符合限制,符合限制的请求数量等方法,内部复杂度进一步提升。

四、bug自查

1、策略问题

第三次作业由于策略问题导致几个强测点出现了超时的问题,进行了两处优化。

第一处为电梯使用first()方法获得第一个乘客时,对等待队列使用了锁方法,导致如果同一时刻有多个相同请求输入时,在第一个请求进入等待队列后电梯立刻苏醒并占用等待队列,只能运送第一个输入的请求,其他请求需要运送完第一个请求后才能开始运送,运行效率极低,可能因此浪费数秒甚至数十秒。改进方法为电梯在从wait状态苏醒后,释放锁并sleep(1)以确保其他同一时刻的请求也进入等待队列后再开始运行。

第二处为电梯上人时,优先选择目的楼层与主请求的目的楼层或当前楼层(主请求不存在时)最近的请求,但当该请求与主请求的运行方向相反时,需要将一个请求运送完毕再折回运送另一个请求,相较使用可能存在的另一部电梯运送第二个请求,浪费了运行资源。解决方法是在电梯内存在主请求时,只接受相同运行方向的请求进入电梯。

2、轮询问题

本单元作业主要碰到两种轮询问题。

一个是在第二次作业使用first()方法中查询等待队列人数时,对等待队列使用了锁方法与nutifyAll()方法,导致当同一楼座或同一楼层存在运行轨迹重复的空电梯时,若等待队列为空,两部电梯会不断地对共用的等待队列使用notifyAll()方法唤醒另一部电梯的wait状态导致轮询。解决方法时加入Waitnum模块,访问该模块获取等待人数,避免影响等待队列。

另一个使第三次作业的横向电梯查询等待队列人数时,得到的时等待队列的总人数,但当等待队列中的所有请求均不符合该横向电梯的停靠限制时,该电梯会永远处于开关状态而无法上人,造成轮询。解决方式时遍历等待队列,只计算符合停靠限制的人数。

五、互测策略

1、有效性

本单元并未使用严密的科学测试策略,仅凭借自己debug过程中碰到的易错点与交流群中讨论的易错点,手动构造测试样例进行测试,严谨度与精准度不高。

2、线程安全

线程安全主要先浏览代码,找到存在线程共用的变量或类型,构造能够使共用变量出现同时访问或频繁变化的请求队列。

3、与第一单元的差异

第一单元由于表达式解析的流程较为固定,不同同学的代码出现共同逻辑漏洞的可能性较高,某些特殊表达的的通用性较强,互测找出bug较为容易。

第二单元对于电梯的运行策略,具体实现方式的灵活性较高,难以找到某个共通的bug,需要对对方的代码进行一定程度解读以定位可能的bug;除此之外,由于多线程的特性,某些bug可能难以复现,幸运的是,在每次互测结束时,课程团队会对互测样例进行进一步的测试以复现bug。

六、心得体会

1、层次化

层次化的设计架构例如本次作业中,Analyse模块将输入请求传递给Building模块,Building模块下辖楼座内的多部电梯,每部电梯对应一个等待队列。这种层次化的设计思路不仅能够为读代码的人提供清晰的逻辑架构,对编写者来说,debug时也能够更加快速的实现定位,同时,同一层次内的内容通常存在较强的复用性,也极大减小了编写代码量,降低了项目难度。

2、线程安全

线程安全的核心在于自已在写代码的时候要搞清楚自己使用的哪些变量或模块是共用的,一旦存在公用变量,且存在写方法,则一定要使用锁方法进行访问;在访问结束时,要根据该变量是否需要唤醒其他线程的wait状态来决定是否使用notifyAll()方法,盲目的唤醒可能造成轮询;在使用wait方法时,一定要确保考虑到了所有需要wait的条件,否则可能出现跳过wait而出现轮询的情况;当线程锁方法内存在对其他锁方法的嵌套关系时要理清楚嵌套顺序,确保不会出现死锁。

posted @ 2022-05-01 15:44  璇璃  阅读(42)  评论(1编辑  收藏  举报