- 本次做业须要模拟一个多线程实时多电梯系统,从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息经过输出接口进行输出。
- 本次做业电梯系统具备的功能为:上下行,开关门。本次多部电梯的可停靠楼层,运行时间,最大载客量都不相同。
- 电梯系统能够采用任意的调度策略,即上行仍是下行,是否在某层开关门,均可自定义,只要保证在系统限制时间内将全部的乘客送至目的地便可。
- 电梯系统在某一层开关门时间内能够上下乘客,开关门的边界时间均可以上下乘客。
此次做业有几个关键点:实时系统,正确调度,多线程交互。这就要求在设计中须要提早定义好运行逻辑,并且经历了第一单元的历练,在第一次做业就须要对后续做业可能出现的状况进行设计——即尽可能最大化程序的可拓展性,下降程序模块之间的耦合程度,这样在需求变化时就能尽可能少的修改代码达到需求。<br>本次做业三个阶段性安排为:java
本单元做业输入输出接口均已给出,所以重点就在于程序进程交互逻辑以及任务分配执行的算法。python
由于有了第一单元设计的经验,为了减小每次的修改量,在每次做业的实现中都尽可能采用逻辑分离式设计,每一个模块独立的从队列中取出信息、处理信息、放置信息(反馈)。模块之间只经过队列交互,而后独立的实现一套逻辑。针对各个部分,设计变化以下:git
addElevator(Elevator ele)
函数添加一部电梯使得电梯可被调度。针对楼层需求,我设置了有序列表使得楼层之间的变化保持连续性:github
List list = Arrays.asList(-3,-2,-1, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
这样在程序中修改楼层因此便可实现楼层之间的连续变化。算法
针对捎带需求,在个人设计中将可捎带断定与捎带执行分配到了电梯本身的线程中,所以这部分基本只对Elevator类进行了修改,在电梯开门、关门、变化楼层、任务开始时遍历Mission队列决定可捎带的任务并判断下一个停靠的目标楼层,以适应不断到来的任务变化。设计模式
须要注意的是,此次由于捎带的发生,须要进行Mission状态的记录——即人在电梯内或者电梯外->进一步为了简化逻辑,将这个状态的判断转化为“Mission在执行队列或等待队列中”。这样作的好处是能够将上下电梯统一块儿来,基于上下电梯须要的电梯行为表现(到达-开门-关门)是统一的这点,可使得电梯逻辑变得简单,至于上或下只须要体如今输出便可。安全
String determine(Mission request)
函数 /** * 考虑:电梯运行状态+电梯选项+目标电梯 * 输入Mission:目标换乘楼层(须要换乘时) * 1. 若是选择只有一个,直接上 * 2. 若是选择有多个,基于“换乘点是一致的”的条件 * 分支树断定(方向、捎带、远近、人多少) * @param request * @return 目标电梯名 */ private String determine(Mission request)另外,由于不一样的电梯线程都须要访问调度器的部分方法,将Scheduler做为了Main类中的静态实例处理,不知道算不算单例的一种实现,但由于Scheduler中保证了线程安全,并且只在主线程中实例化,所以这个静态实例变量能够正确使用
this.availableFloor = new ArrayList<>(); this.availableFloor.addAll(floors);来断定可停靠的楼层,至于具体是哪些,在建立电梯时指定,电梯中还增长了相应的接口进行楼层判断。实现整套逻辑。
public Boolean canDeliver(int floor) { if (!this.availableFloor.contains(floor)) { return false; } return true; }相应的在Scheduler中提供了函数判断是否可直达
/** * 可直达? |--yes--> 使用直达电梯 * |--no---> 拆分红两阶段 * * 当前阶段性任务有多部电梯可完成:1.当前方向上可捎带? ---> 2.电梯中人数较少? */这样就可以较为合理地规划电梯的任务分配并处理好换乘状况。
最终获得的总体逻辑的时序图以下:多线程
本次做业第一阶段和第三阶段都未发现bug,第二阶段强测中发现了一个bug,主要是由于第二阶段对调度性能要求较高,可捎带的状况必需要捎带上才行,而我在设计Mission队列以及Elevator正在执行的Mission存储两部分同步不是很好,致使Mission队列中有任务,却由于检查不及时Elevator没有检查并执行这个任务,尤为是某一层有不少人上电梯时(同时有不少人同一楼层的用户请求到来)。这个bug的出现和输入的时机有关,所以本身测试的时候没有检测到。<br> 第三阶段我花费了很长时间在debug上,由于评测机一只返回RUNNTIME_ERROR,但个人程序并不会出错,为此,我还实现了简单地随机测试脚本:架构
import random pair = [] l = [-3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] for i in l: for j in l: if i == j: continue else: pair.append((i,j)) n = 0 time = 0.0 with open("data.txt", 'a') as f: for t in random.sample(pair, 50): print("[{:.1f}]{}-FROM-{}-TO-{}".format(time, n, t[0], t[1])) status = f.write("[{:.1f}]{}-FROM-{}-TO-{}\n".format(time, n, t[0], t[1])) n += 1 if random.random() > 0.6: time += random.random() f.write("\n")
以上使用python自动生成数据,借助Mistariano提供的黑箱接口能够进行简单地测试,数据生成能够经过调节参数控制app
@echo off set num=0 :start set /a num+=1 echo ================No. %num% time======================= >> err.txt python gen.py | "C:\Program Files\Java\jdk-11.0.2\bin\java.exe" -cp out\production\oo_course_e_2019_16071064_homework_6;..\..\elevator-test-suit-0-3.jar Main >> err.txt 2>&1 echo ================ over ======================= >> err.txt goto start
虽然使用了大量的黑箱测试个人代码却依然有RUNTIME_ERROR的问题,因而我又尝试其余的debug,终于发现是由于程序退出时Scheduler发生了轮询致使CPU时间超时(这一点评测机并无反馈,并且错误种类不该该是这个啊),这里,JProfile的线程分析帮了我大忙
这个界面显示了每一个线程实际运行的时间,若是有线程使用轮询致使CPU时间过程能够明显的看出并直接定位。
相比较以前的设计,这个单元我认为做为比较好的是在第一次代码就肯定了整个的设计框架,也证实在新需求到来时能够较快的适应改动。
从图中能够看出各个类的功能划分是比较清晰地,关于共享对象的设计以及线程类的实现是和预期设计一致的。并且每个线程类都不依靠其余线程,是一个独立的逻辑体,这大幅下降了模块之间的耦合性,也使得代码逻辑更为清晰易懂。
代码量分析
此次的代码相比较第一单元有了很大的改观,首先是类的代码量,大部分功能类的代码量分布仍是比较均衡的,代码量最多的类源码没有超过300行,并且在完成过程当中我也尝试使用Javadoc风格的函数与类功能注释,注释比例有了很大的增长。重要的是,使用这样的注释以后在下一个阶段须要更新代码的时候可以很快的回忆起函数的功能与实现。<br>此次类间代码量的均衡得益于功能上的分工明确,即将不一样的功能实现交由不一样的模块完成,但其中由于电梯的运行策略最为复杂,代码行数较多。但总体上仍是比较合理的。
方法复杂度分析 此次代码的复杂度分析呈现出如下结果,共出现了三个复杂的方法,一个复杂的条件判断,一个长参数列表问题,这些问题的应该是能够解决的。总体来看,代码的循环复杂性(CC)为2.49122807,其中循环复杂性最高的函数依旧是iterMission,高达13。这个函数的功能是遍历Mission队列并添加Mission到电梯的执行队列中,从这一点来看,这个函数仍是有能够简化的空间的。
Class Name | Method Name | Code Smell |
---|---|---|
Elevator | Elevator | Long Parameter List |
Elevator | checkDirection | Complex Method |
Elevator | iterMission | Complex Method |
Scheduler | run | Complex Method |
Scheduler | determine | Complex Conditional |
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC |
---|---|---|---|---|---|---|
域数量 | public域数量 | 方法数量 | public方法数量 | 行数 | 类加权方法数 | |
Elevator | 8 | 0 | 15 | 4 | 302 | 58 |
Main | 1 | 0 | 3 | 3 | 37 | 4 |
Mission | 6 | 0 | 10 | 9 | 118 | 17 |
Scheduler | 5 | 0 | 11 | 7 | 200 | 33 |
Submission | 2 | 0 | 2 | 2 | 46 | 4 |
ElevatorState | 9 | 0 | 16 | 16 | 121 | 26 |
这是我第一次接触多线程程序,以前只是在学习操做系统的时候了解过原理,但并无动手写过多线程程序。经过此次实践我不只掌握了多线程程序的设计方法,并且了解到设计模式在多线程交互中的关键性。不管是线程安全仍是死锁预防,或者是线程之间wait/notify的等待唤醒机制,都让我对线程交互有了更深层的理解,并且认识到使用JProfile分析程序运行状态进行优化的必要性。<br>真正测试代码时,白盒测试是比较直接的方式,但黑盒测试优点也能提供意想不到的效果与便捷性,并且从某种程度上讲更能达到压力测试的效果。<br>另外,在设计之初就考虑可扩展性是颇有必要的,一套好的架构必定是可以支持不断扩展的架构,进行好架构设计对功能扩展不管是工做量仍是安全性都有很好的帮助。