OO第二单元总结(电梯调度)
前言
从进入电梯单元开始,我们就来到了多线程的世界,这是一个充满乐趣、充满荆棘的领域。这段时间以来,通过三次作业以及课上实验的训练,我已经逐步掌握了多线程的相关知识,我不得不感叹,在使用多线程以后,我的程序的效率得到了大大的提升,并且更加具备系统性、工程性。经历了反复的构建,从最初的什么都不懂,到能独立完成生产者消费者模型的构建,再到学会使用调度器、对队列进行调控,终于直至现在,我能够在这里对电梯的三次作业进行总结与回顾,从而进一步加深对多线程的理解,明晰各种有关多线程的类、方法与关键字的使用。好了,话不多说,那我们就开始吧!
第一次作业分析
内容介绍
本次作业的基本目标是模拟单部多线程电梯的运行。
电梯参数说明
可到达楼层:1-20层
初始位置:1层
数量:1部
移动速度:0.4s/层
开门速度:0.2s/次
关门速度:0.2s/次
限乘人数:6人
到达模式说明
第一行指示乘客的到达模式,格式为一个特定的字符串,仅可能为以下三种:
- Night:乘客在Night指令到达时一次性全部到达,终点层都是底层。
- Morning:乘客到达间隔不超过2s,起始层都是底层。
- Random:允许任何到达情况出现。
思路分析
(在此说明一下,此处的思路以及以下的程序分析和架构图示都是我修复bug之后的设计,因为最初的设计性能实在太差,在强测中得了0分,没有分析的必要,所以此处展示的是修复bug,即重构之后的架构设计)了解题目要求后,我开始分析思路,由于包含三种模式,因此我们要逐个进行探讨。首先,对于Night模式,乘客是同时到达的,因此我们可以考虑将这些指令全部放在一个Arraylist中,收集完毕后(即nextRequest为NULL时),我们就可以准备运行电梯了,由于限乘6人,因此我们可以考虑每次运送6个人,那么同时为了保证效率,我们要将Arraylist中的指令根据起始楼层进行排序(从高到低),然后逐次运送,这样做的好处是为了防止电梯在楼层之间往复运送,降低了效率。而从高到低排序的话,就可以保证电梯上行一次、下行一次就能送完6人,并回到一楼运送下一批。好,接下来是Morning,这时每两个乘客的到达间隔不超过两秒,此时貌似也没有什么明显的方法,那就等人数满6个人时再运行电梯,一样的每次运送6个人,若人数不满6人,就等一等(当然,读入NULL时就停止,然后立刻运行电梯),这时,每次运送6个人,一样的,将这6个人的去往楼层进行排序(从低到高或从高到低),这样运行下来的效率还不错。最后是Random,这个没有任何的规律,为了保证效率,只能借用前人的算法,比如ALS调度策略、LOOK算法等,他们的效率都很好,比较合适。OK,这样看来,本次作业就差不多解决了。
架构分析
架构图示
UML类图
UML协作图
程序分析
同步块与锁的处理
为了保证线程的安全,我将请求队列中的方法均加入了synchronized关键字进行保护。同时,我合理的使用了wait、notify等方法来保证线程的同步以及死锁的避免,较为成功的处理了这些多线程有关的问题。
调度器设计
第一次作业只有一步电梯,事实上无需设计调度器,毕竟输入线程得到的指令可以直接存放到请求队列中。当然,想要设计一个调度器也并不算复杂,只需要加入一个等待队列即可,即构成等待队列——调度器——处理队列的顺序结构。
bug分析
自身bug
再次说明一下,以上架构为我修复bug之后的架构,我之前的架构思路如下:Morning和Night处理方式一样,都是等所有指令到达后再开始运行电梯,每次运送6个人,没有进行排序。Random采取傻瓜电梯的模式,一条一条指令的运行,没有使用任何的调度策略。这样过了中测,因为中测的限制时间是210s,然而强测却得了0分,因为性能太差,超出了Tmax限制(基于ALS策略的限制时间),即只要你的运行速度比ALS策略的慢,那么你强测就不可能得到分,因此本次作业我强测一个点都没有通过,所以没能进入互测屋。最后,在修复bug的阶段,我对性能进行了优化,采取了电梯调度策略,终于通过了强测的所有测试点。
他人bug
由于没能进入互测屋,因此没能进行bug的hack。
第二次作业分析
内容介绍
本次作业要求模拟多部同型号电梯的运行,并要求能够响应输入数据的请求,动态增加电梯。
电梯参数说明
可到达楼层:1-20层
初始位置:1层
数量:初始3部(ID分别为1
, 2
, 3
),最多5部
移动速度:0.4s/层
开门速度:0.2s/次
关门速度:0.2s/次
限乘人数:6人
到达模式说明
第一行指示乘客的到达模式,格式为一个特定的字符串,仅可能为以下三种:
- Night:乘客在Night指令到达时一次性全部到达,终点层都是底层。
- Morning:乘客到达间隔不超过2s,起始层都是底层。
- Random:允许任何到达情况出现。
思路分析
本次作业相对于第一次作业增加了电梯数量,第一次的电梯只有一部,第二次作业的电梯初始为3部,然后可能出现增加电梯的指令,最多增加到五部。考虑到电梯数量的增多,效率肯定会进一步提升,我首先考虑了Random的情况,因为此时比较好分析,最初如果是一部电梯,则采用一个Process线程即可,此时为三部电梯,则采取三个Process线程,如果遇到新增电梯的指令,则再新建一个Process线程即可,然后每一个Process的处理都采用ALS调度策略,在性能上是可以满足强测要求的。对于调度问题,我设置了一个等待队列,然后在一定的时刻将得到的等待队列进行调度分配,采取轮流的方式,即平均分配给每个电梯的处理进程,这也是为了效率而考虑的,从而每个电梯分担相同的工作量,最终进行运行。然后是Morning和Night模式,由于受到前面Random的思路启发,我思考是否可以将Random的处理方式运用到Morning和Night上,因而,本次作业有两种选择,一种是对于Morning和Night进行特定的调度,另一种是直接将三种模式都设置为ALS处理策略,对我而言,考虑到工作量的问题,我选择了后者(带有一定的冒险性),事实证明,三种模式采取同样的ALS策略后,最终性能还挺好,强测的数据点均能达到99分左右,这也令我感到有点惊喜。那这样的话,本次作业就还比较容易,只要处理好多个线程的调度分配即可。
架构分析
架构设计
UML类图
UML协作图
程序分析
同步块与锁的处理
第二次作业中包含调度器,并且使用了等待队列和处理队列,在同步块和锁的处理问题上,我将输入线程的等待队列加了锁,在调度器中对等待队列和处理队列加锁,并且对等待队列类和处理队列类中的方法都加了synchronized进行保护,同时要注意notify和wait的使用时机,保证程序能够顺利的运行和结束。
调度器设计
设置调度器的目的是为了将等待队列中的请求分配给不同的电梯请求处理队列,比如本次作业有3~5个电梯,那么就应当设置对应数量的电梯处理队列,当等待队列中存在请求时,则将其中的请求按照策略分配到各个电梯处理队列。此处我采用的方法是轮换,比如,目前有3部电梯,有6个请求,则我会把第1个请求给第一部电梯,第2个请求给第二部电梯,第3个请求给第3部电梯,然后第4个请求再给第1部电梯,等等,分配完之后将等待队列清空,等待下一次分配。
bug分析
自身bug
本次作业在强测中没有出现错误,但是在互测中被hack到了一个bug,就是在电梯的人数满6个人之后,我的电梯仍然可以继续进人,之后我分析程序发现,我在设置容量限制的地方出现了问题,修改了bug之后,就不存在这个错误了。
他人bug
在互测中,我hack了一个人,他的错误体现在RTLE上,即线程没有正常结束,我的数据只包含了三条指令,一个模式指令、一个加电梯指令、一个乘客指令,他的程序在运行时,首先启动了三个电梯线程,而在运行完指令之后,有两台电梯线程却没有正常结束,说明他在处理线程的结束问题上出现了错误,需要进行修复,这也对我的程序设计提供了相关经验。
第三次作业分析
内容介绍
本次作业要求模拟多部不同型号电梯的运行。型号不同,指的是开关门速度,移动速度,限载人数,以及最重要的——可停靠楼层的不同。具体的型号参数在下面给出。
电梯参数说明
可到达楼层:1-20层
初始位置:1层
数量:初始3部((ID,类型)分别为(1,A),(2,B),(3,C)),最多5部
开门速度:0.2s/次
关门速度:0.2s/次
电梯内部初始乘客数目:0
型号参数:
型号 | 到达楼层 | 移动速度 | 限乘人数 |
---|---|---|---|
A | 1-20 | 0.6s/层 | 8 |
B | 奇数层 | 0.4s/层 | 6 |
C | 1-3,18-20 | 0.2s/层 | 4 |
到达模式说明
第一行指示乘客的到达模式,格式为一个特定的字符串,仅可能为以下三种:
- Night:乘客在Night指令到达时一次性全部到达,终点层都是底层。
- Morning:乘客到达间隔不超过2s,起始层都是底层。
- Random:允许任何到达情况出现。
思路分析
这次作业相对于上次作业增加了型号的要求,一共三种型号。在上次作业的基础上进行扩展时, 有几个地方需要格外注意,一是移动速度、限乘人数要根据型号进行调整,另外是到达楼层的不同,从而对调度策略也需要进行调整。对于移动速度、限乘人数的分类,只需要在涉及到这两个参数的地方,加入一个型号的判断即可,即判断当前电梯是哪种型号,然后设置对应的参数即可,此处没有难度。难度是在调度策略上,由于电梯运行的楼层有了限制,因此在设计调度器时,不能再单纯的采用平均分配的形式,而是应当采取一定的方法。我的想法是,如果乘客的起始楼层和到达楼层均处于1-3,18-20之间,则放入C类型的电梯队列中,如果乘客的起始楼层和到达楼层均为奇数,则采取B类型电梯,如果都不是,则使用A类型电梯。这种方式没有采用换乘策略,但是效率也还很好,毕竟ALS策略已经能够很好的符合这几次作业的时间要求了。
架构分析
架构设计
UML类图
UML协作图
程序分析
同步块与锁的处理
第三次作业和第二次作业的处理方式基本相同,没有太大的改动。
调度器设计
第三次作业由于有了楼层的限制,我采取了按类型分配,大概如下:
没有采用换乘策略,因此效率以及可拓展性还较为欠缺,需要后期再进行改进。
bug分析
自身bug
本次作业在强测和互测中均未发现bug。
他人bug
本次未能hack到他人的bug。
心得体会
电梯单元,简单来讲,就是多线程+调度策略。多线程就是要设置输入线程、调度线程和处理线程,这其中要求我们深入掌握线程的实现方式,了解如何去保证线程的安全。调度策略设计到算法知识,需要我们平时多学习一些算法,要具备将算法转换成实际代码的能力,这需要我们不断的积累和训练。本单元最难的一部分就是第一次作业了,因为一开始就要求我们采用ALS策略,并且还需要建立多线程架构,这两方面的任务导致时间比较紧张。而只要建立起ALS调度策略之后,第二次作业和第三次作业就都没有什么难度了,只需要添加几个线程就好了。总体来讲,本单元我的收获还是很多的,掌握了多线程这一有利的工具,同时对算法有了更加深刻的认知,事实上,除了ALS调度策略之外,还有LOOK算法,后面如果有时间我也会去考虑实现它。电梯调度系列作业终于圆满结束了,我也意识到了自身存在的一些弱项,在未来的学习生活中,我也会努力的完善自己,增强自己各方面的知识水平与应用能力,不断提高以适应未来可能会从事的相关工作。