OO第二单元多线程电梯总结

1 第一次作业

1.1 任务概述

第一次作业要求完成单部可捎带电梯的模拟。

1.2 设计策略

第一次作业中,我对于多线程编程理解还不深刻,只能先模仿实验课练习的规范写。

我采用了经典的生产者-消费者模型,由Input线程产生请求,放入调度器对象RequestQueue中,然后由Elevator线程从中取出请求执行。

在调度器放入、取出请求以及结束置位的接口上,均以this为锁进行同步,保证调度器是线程安全类。

在电梯中,我维护了一个工作队列存储所有已经进入电梯的请求,使捎带方面逻辑较为清晰。

调度算法我采用了课程组提供的ALS,电梯空闲时获取等待队列中最先到达的请求为主请求,然后在电梯到达每一层时判断这层是否有可捎带请求,以及电梯内是否有请求到达,进行IO交互。只有电梯把当前工作队列中的所有请求全部送达后,才会重新寻找主请求。

当电梯中无人、输入已经结束且等待队列中无请求时,电梯线程结束。

1.3 UML类图

 

1.4 UML时序图

1.5 度量分析

代码量:

方法复杂度:

在判断可捎带请求上逻辑较复杂。

1.6 测试阶段

中测:第一次提交AC。

强测:没有出现bug,但性能分很差。后来分析了一下,对于单部电梯,采用SCAN/LOOK算法或SSTF(贪心)算法在性能上会好很多。

互测:没有hack/被hack成功。

2 第二次作业

2.1 任务概述

在第一次作业的基础上,新增了动态初始化电梯数量(1~5部),引入了负数楼层和电梯最大载荷。

2.2 设计策略

总体架构不变,初始化时由Input线程根据输入来创建电梯线程。

在获取主请求的接口中,改用贪心算法,将电梯当前的楼层作为状态参数传给调度器,调度器寻找一个距离最近的请求作为主请求。

可捎带请求的判断逻辑修改为只会捎带与电梯运行方向、主请求方向均同向的请求。获取可捎带请求时,将电梯当前可继续装载的最大人数、运行方向、主请求方向作为状态参数传给调度器,调度器寻找所有满足条件的请求作为捎带请求。

在多部电梯中,我使每部电梯的请求获取尽可能的动态、随机,简化其捎带任务,提升单部电梯的利用率。

2.3 UML类图

2.4 UML时序图

2.5 度量分析

代码量:

方法复杂度:

还是老问题,电梯IO判断项目较多较复杂。此外由于贪心寻找,调度器的获取主请求接口也比较复杂。

2.6 测试阶段

中测:第一次提交AC。

强测:没有出现bug,性能分较好。看来多部电梯使用贪心算法效果是不错的。

互测:没有hack/被hack成功。

3 第三次作业

3.1 任务概述

第三次作业在第二次的基础上规定了三种电梯类型,每种类型的可达楼层、运行时间和最大载荷均不同,部分请求需要换乘,并且可以动态增加电梯。

3.2 设计策略

在可达楼层、运行时间和最大载荷的不同方面,只需要在电梯中增加相应的字段,在工厂类中按需生成即可。具体判断逻辑与第二次作业相同。

对于换乘请求,在调度器中新增一个HashMap类对象Wait作为等待队列。当新请求到来时,判断其是否为直达请求,如果是则直接放入请求队列中,如果不是,则按照RequestParser类的分析结果给出一个换乘楼层(所有非直达请求都能分为两段直达请求),构造两个阶段的PersonRequest,第一阶段存入Queue中,第二阶段存入Wait中。当有请求完成时,在Wait中按id查找其是否有第二段请求,如果有,则将第二段请求移至等待队列中。

调度算法完全不需要改动,延续第二次作业的贪心算法。

电梯只有到达自身的可达楼层时,才会执行IO函数判断捎带和进出。

对于单个电梯,当其工作队列为空、等待队列中没有自身可以直达的请求,且输入已经结束、Wait中没有第二阶段请求时,电梯线程结束。

3.3 UML类图

 

3.4 UML时序图

3.5 度量分析

代码量:

方法复杂度:

3.6 测试阶段

中测:第一次提交AC

强测:没有出现bug,性能分较好。

互测:没有hack成功,被hack成功了一个RTLE的点,但是观察错误输出发现中间莫名停顿数次,经过反复的本地测试都无法复现且本地与评测结果相差甚远,且原封不动再次提交即修复,搞不明白。

4 SOLID原则分析

对于第三次作业,结合SOLID设计原则一一进行分析

SRP(单一职责原则):

对于电梯类,设置了IO、人员进出、电梯运行、更新方向等方法;对于调度器类,设置了放入请求、分割请求、获取主请求、获取捎带请求等方法,分工较为明确,顶层逻辑清晰,没有一个方法负责两件工作的情况,同一个类中的方法也仅用于维护自身状态。

OCP(开闭原则):

如果新增需求是有关电梯状态的限制,我的设计是能够在不改变现有代码的基础上新增功能的。但是如果是更为宏观的要求,比如电梯的检修、增设楼梯、更复杂的请求实现,那就不可避免的要改动现有架构,说明在开闭原则方面还可以继续完善。

LSP(里氏替换原则):

由于电梯类的方法目标较为清晰,在不同类型的电梯中含义均相同,只是更换了数值。且对于不同类型的电梯,我并没有编写多个类,而是在一个类中增加字段,通过工厂模式构造不同的电梯,可以满足LSP原则。

ISP(接口隔离原则):

虽然本单元中我并未继承除了Runnable外的任何接口,但是电梯类中的方法到不同类型的电梯的映射过程可以看做是接口的实现。在这方面,由于单个方法的功能相对于整体都是不可或缺的,例如每个状态的维护、人员IO都有且仅有一个函数来负责,其内部的子函数也都有各自独立的功能进行复用,所以可以说接口是隔离开的。

DIP(依赖倒置原则):

本单元作业中,最底层的模块是共享对象Queue类以及一些采用单例模式的功能类如工厂类、楼层类和请求分析类等,而这些类可以被一个或多个线程类共享,例如Queue类和楼层类被输入线程和电梯线程共享、工厂类被输入线程实例化。但是调度器类中并没有存储每个电梯的状态信息,它无需了解电梯状态,只需要电梯根据自己的状态向调度器获取特定请求即可,没有循环依赖,在DIP原则上做的较好。

5 总结与反思

本单元是我第一次接触多线程编程,感觉同以前的编程,甚至同上一单元的编程相比都有着巨大的改变。每次任务不再需要逻辑超复杂的方法和花里胡哨的类,但是编写的思维难度却只增不减,因为以人脑这个单线程去思考JVM的多线程是很有难度的。特别是在一开始,写第一次作业的时候,对于线程之间、线程与共享对象之间的通信,经常能把我想晕了。不过到了第二次、第三次作业的时候,由于掌握了多线程编程的技巧和逻辑,我很好的延续了以往的架构,很快就完成了任务,摆脱了第一单元重构三次的魔咒。多线程中,最容易出的错就是死锁、死循环、轮询、停不下来等等。想解决这些问题,就要搞清楚wait、sleep、notify、notifyall如何使用、使用在哪、为什么在这里使用,多思考当多个线程都执行这条语句时会发生什么,避免乱用,以上的问题都可以轻松避免。

在自动测试方面,我依然延续了第一单元的做法,那就是不写(划掉)。笔者在完成了课内的所有规定任务之后脑子里只剩下玩,不得不说确实是一条懒狗。当然,为了在自动测试方面不落下,我学习使用了Jprofiler这个监控软件来确保线程安全,另外更大部分的自动测试是我找同学白嫖了好用的包和脚本,一跑就灵(逃)。在下一单元中,我们会学习JML这种新的语言。到时候希望我能多多学习大佬们写的评测脚本,自己也写一些简单的评测机,做一点贡献(上一单元就这么说的)。

 

posted on 2020-04-16 12:06  DawnCat  阅读(185)  评论(0编辑  收藏  举报