BUAA OO Unit2 电梯调度

此次做业完成了一个开环可选层电梯调度系统。第二次迭代加入了容量限制、多部电梯,第三次迭代加入了电梯楼层分工、增添电梯请求。java

1. 系统架构

graph LR MainClass--Requests-->Schedule Executor--Notify-->Schedule Schedule--Update-->Executor MainClass--Create-->Elevators Schedule--Check-->Elevators Executor--Operate-->Elevators Schedule--Adapt-->Method
  • MainClass用于对各个子系统的组装,发送请求至Schedule
  • Schedule用于接收来自MainClass、Executor的信息,更新状态
  • Executor监听Schedule的改变,使用单线程操纵全部电梯,同时将操做结果返回Schedule。
  • Method为策略模块,实现同步控制与策略模块的分离,能够用于适配不一样的策略。

2. 同步控制

定时控制

同步控制的主要由Schedule设定,由Executor执行。并发控制的核心为一个阻塞定时监听器,可实现可调整的定时控制。这个模块的实现方法参考了java.utils.Timer算法

private long scheduledTime = Long.MAX_VALUE;

public synchronized void retrieveNextAction() throws InterruptedException {
    long curTime = System.currentTimeMillis();
    while (curTime >= scheduledTime) {
        wait(scheduledTime - curTime);
        curTime = System.currentTimeMillis();
    }
    scheduledTime = Long.MAX_VALUE;
    return;
}
  • scheduledTime变量存储下一次计划动做的时间,规定该时间只减不增。即只保障不存在早于scheduledTime的动做。缓存

  • retrieveNextAction为阻塞方法,用于Executor阻塞获取下一次的动做。安全

  • 存在新的预期执行的动做时,更新scheduledTime,调用notifyAll(),retrieveNextAction重置等待时间/取消等待。多线程

  • 没有设定动做时序队列,每次完成时,须要检索所有可能的动做,并根据执行状况设置下一个scheduledTime架构

  • 这样作的主要优点在于没有用到Thread.sleep方法,能够实现任意时刻对请求的及时响应。并发

另外,除Schedule外,另外一个共享变量为Elevator。这里Elevator类仅用做记录电梯状态,提供改变电梯状态的接口。全部操做由Executor根据Schedule产生,并在敏感操做(如关门-移动序列)的执行过程当中进行了上锁,屏蔽Schedule类的全部请求,保证一致性。机器学习

对于电梯移动,在Executor中对Schedule上锁保证安全性。对于人员流动,使用ConcurrentLinkedList定义可执行队列保障安全性,并在电梯移动时清空。Executor请求完成后调用相应方法通知Schedule,根据策略更新预期动做。函数

UML协做图 (Sequence Diagram)

elevator seqUml

* 协做图中动做可能不知足正确性,但反应了动做之间的时序关系。性能

* 2个线程为MainClass主线程和Executor执行线程。Schedule、Elevator为共享变量,被动进行数据调取。

* 还存在Schedule-Elevator之间的调用。

同步控制相关版本迭代

  • 第二次做业:
    • 去除了没必要要的分支,简化了定时阻塞器、终止判断的执行流程,取消了没必要要的PersonAction包装。
    • Schedule类中增长了notifyPersonCheckedIn,notifyPersonCheckedOut方法,以适应电梯容量受限致使的策略变化。
    • Schedule类内中的电梯相关数据由单一数据转变为列表控制。访问数据时使用电梯索引值访问。
    • Executor类内,每次检测到Schedule类存在新动做时,由检查单一电梯转变为检查所有电梯。实现宏观上的电梯并发效果
  • 第三次做业:
    • Schedule类中增长评判:人员是否须要从电梯中出去。前两次做业中只要知足与人员请求目标楼层相等都要出去。
    • Schedule类中增长方法:AddElevator。在所有数据列表中增长一项便可。
    • 优化代码结构,Schedule类使用泛型访问电梯接口,实现不一样种类电梯和对应策略类的匹配。

3. 架构设计

本节以第三次做业的版本为准讨论了架构设计和可扩展性。

Unit2 Task3 Interface Uml

须要实现2个接口:IElevator、OperationMethod。

  • OperationMethod须要对canGetIn、canGetOff、nextfloor询问作出答复,接收addElevator、 notifyRequestReceived两个辅助更新请求。
  • IElevator需实现运行时间查询、电梯状况查询、电梯操做、人员操做相关方法。
  • 实际上Elevator类已经实现IElevator,一些特殊种类的电梯能够进行重写经过相关方法实现。同时Elevator的类型能够借助泛型经过Schedule传递到OperationMethod,实现策略类与电梯的一一对应。

第三次做业经过继承的方法已经实现了楼层限制电梯,以及针对3中楼层限制电梯的方法不一样方法。

功能-性能平衡

因为采用了策略与调度分离的架构,使得在实现相应功能的同时,为策略的制订保留了很大的空间,同时容许对不一样的策略进行试验。但在策略制订的过程当中,可扩展性作的不是很好,功能的调整每每须要策略作出较大的调整,在前几回做业中均进行了策略重构,可能也与使用的策略具备较高的特殊性有关。

准则异常

单一责任准则:Schedule类和Executor类是全部请求实现的关键路径,对进一步添加请求不理想,应考虑创建请求接口,将请求执行步骤抽象为【电梯操做-调度更新-时间等待-新的请求】。

接口隔离:应考虑将策略接口拆分红发送策略接口、接受数据改变接口。

需求扩展方法

对于后续的扩展,能够根据扩展的类型进行版本迭代。

  • 级别1:具备不一样楼层限制规则的电梯、更新评价方法。

    扩展ElevatorRestricted.Type枚举类型,编写新的MethodTypeD,调整MethodTask3分配策略。

  • 级别2:新的限制规则电梯/楼梯、不定时电梯。

    创建新的Elevator类,重写Elevator相关方法,较大规模的调整MethodTask3分配策略,编写新的MethodTypeD。

  • 级别3:新的请求类型。如:停用电梯、更改请求

    在Schedule类中增添新的方法适配请求,检查Executor类的执行过程是否符合新条件下的请求要求。考虑IElevator、OperationMethod接口中是否须要增添方法适配请求。

    若是请求的类型较多,能够考虑创建请求接口,将请求执行步骤抽象为【电梯操做-调度更新-时间等待-新的请求】过程,在Executor和Schedule中设立相应设施。

  • 级别4:新的调度维度。如:水平电梯

    创建新的调度策略。重构电梯类,将一维操做改成2维,并在全部访问位置进行修改。

4. 度量分析

类复杂度(Weighed Method Complexity)

第一次做业 第二次做业 第三次做业
wmc1 wmc3

第一次做业中对2种策略进行了测试,最终选用了略微优化的Als策略。另外一种策略过于复杂并且效果不是很好。第二次做业中对ALS策略进行了调整,以适配多电梯,效果相似于LOOK策略,但实现后致使MethodAlsMultiple复杂度太高。第三次做业将策略分红主策略和子策略,类复杂度能够接受,MethodAlsMultiple基本未做修改。

方法复杂度(Cyclomatic Complexity)

第一次做业 第二次做业 第三次做业
vg1 vg2 vG3

这几回做业设计中Executor线程须要完成所有动做的发送,工做量较大,一开始放在了run方法,尽管后来作出了一些步骤的提取,仍旧有不少步骤被留在了run方法中,致使复杂度较高,这个是不恰当的。

另外,在第三次做业中,部分优化方法直接进行了重复逻辑的复制粘贴,在一个方法内引用了较多外部参数,还须要进一步重构。

循环依赖

第一次做业 第二次做业 第三次做业
dep1 dep2 dep3

第一次做业因为分包不太恰当,致使根目录和子包出现了循环依赖,同时策略类和调度器也存在循环应用。第二次做业调整了Timer类的分包,创建了策略类和调度器的共有引用类,解决了这个问题。第三次做业因为子类实现了主类的内部接口,而主类有又持有子类的引用致使循环引用(因此根据规范应该把内部接口放在外部,或者编写额外的工厂类组装主类、子类)。

类结构图

  • 第一次做业:实验了2个方法:ALS和动态规划。PersonAction抽象的不太恰当。Timer类分包不太恰当。

uml1

  • 第二次做业:PersonRequest嵌套提供的PersonRequest,用于实现一些特殊/虚拟的的请求。层级关系较第一次做业更好。但Elevator有待进一步抽象。

uml2

  • 使用接口-继承的方式进行了迭代更新。层次结构进一步优化。不过MethodTask3和SubMethod关系有待调整(以前已经阐述)。MainClass引用过多,应创建初始化相关类。

uml3

5. BUG分析

第一次做业中,中测阶段的bug主要为题目理解不当,没有正确的使用输出包,强测互测没有发现Bug。

第二次做业中,强测互测阶段也没有发现Bug。

第三次做业中,强测阶段没有发现bug,但互测阶段发现了两个较为严重的bug。一个是电梯容量定义错误,然而在以前的中测强测中因为数据较为均匀、稀疏,加上C电梯利用率较少没有被测出来,自测时也没有再作检查。另外一个是上电梯后,尽管已经通知了调度器更新,但在调度器更新函数的编写时没有考虑换乘问题,致使换乘时可能出现一我的上两个电梯的状况,在更新函数中加入一个删除相同请求就能够了。这个可能缘由是前两次做业调度器编写时内联逻辑过多致使鲁棒性不高,环境改变后忘记了相关内联逻辑。

6. 测试

简易评测样例定时读入类

使用本身编写的ScannerX实现了一个简易的定时输入,知足基本的测试需求。在test模块中,将main函数中的ElevatorInput替换为ScannerX便可测试。定时输入使用sleep进行定时,在第一次调用时初始化,根据当前时间进行修正。

测试策略

  1. 手动数据:构造少许边界性数据
  2. 随机测试:生成一些数据,在时间边界投放。做业3中随机测试偏向于高频生成边缘楼层(如3楼、-3楼、15+楼等)。
  3. 覆盖性测试:覆盖做业3中全部的换乘状况

实际上第二第三次互测中均没有发现其余人的bug,多是强测成绩比较好分到的屋大佬较多...第一次互测中因为定时输入没有作时间修正致使hack数据时间不许确,没有hack上。

和第一单元测试相比,此次测试评测机搭建更为麻烦一些,不像第一单元直接使用sympy计算就能够验证正确性,同时样例投放也更加困难一些。这致使测试的效率降低了一些。

7. 心得体会

多线程程序

此次是第一次完整地编写多线程协同程序。在并发程序的编写中,须要仔细地考虑冲突问题,但若是对于每个语句都进行考虑显然是不划算的。一方面是经典模型引入,例如读者、写者问题,生产者、消费者问题的解决方法,一方面是系统架构的独立性。若是将一个大的并发问题拆解成几个独立的链条,仅需考虑链条与链条的并发、链条内部的互访问,即可以“分而治之”,运用相关模型逐个击破。

优化策略

在第一次做业中一开始尝试使用动态规划的思想进行优化,然而实际上效果并非很理想,并且复杂度很高,后来采用了改进的ALS策略。实际上和缓存机制相似,因为咱们并不知道将来可能存在什么请求,所以没法将其转化成背包问题,在请求来临前就实现最优调度。ALS和LOOK等方法看似和动态规划相比不是最优,但和SJF、FIFO等算法相似,都是一种基于已知内容的可行优化策略。

在第二次第三次优化的过程当中,基础上采用了ALS算法的定向捎带原则,并借鉴粒子群优化算法的思想,对空闲电梯制定了打散策略,空闲电梯运动方向受到来自于其余电梯,与电梯容量、距离相关的压力的影响,使电梯容量倾向于在电梯运行空间内均匀分布,以减小调度时间指望。同时第三次做业不一样种类的电梯制定了单独的策略,一个重要的策略是减小A类电梯空转时间。最终在强测中均得到了99分以上的成绩。

这两个方法还有很大的改进空间。这几回做业优化的主要路线都集中于如何应对将来可能的请求,而对当前已有的请求策略并非最优。第三次做业的请求分配也存在问题,可能与当前最优偏离较大,但在随机数据状况下偏离不太明显。另外一个优化路线是何泽欣同窗的基于模拟的估价函数,模拟不一样状态下电梯运行时间以完成评判。若是能够创建一个历史与将来的请求相结合的分配方法可能会有更好的效果。

实际上优化一个策略函数是一个比较复杂的问题,特别是针对将来的请求指望。或许能够有一些机器学习的方法,例如梯度降低法加以解决,完成对策略函数的拟合。

P.S.若是针对强测,考虑到助教、同窗之间的博弈,均衡应该是彻底随机的数据,这个也是打散算法较为重要的缘由。

相关文章
相关标签/搜索