一.设计策略及程序结构分析
1.第一次作业
第一次作业是需要我们用多线程模拟一个实时电梯系统,功能比较简单正常,但要有捎带功能,我采用的调度策略便是指导书上提供的ALS调度策略,采用消费者-生产者模式,生产者即输入类,不断输入人的电梯请求,消费者就是电梯类,不断接受输入过来的电梯请求,并不断执行下去,中间的仓库类便是调度器,负责存储输入类即生产者提供的请求,并经过一定的调度策略将电梯请求交给电梯去执行.
但调度器要获取电梯的实时状态,却不能一个线程直接调用另一个线程,所以我是采用了这样的设计策略.电梯存储自己的状态信息,并在电梯线程的每一个执行循环开始时,获取运行方向,电梯只负责是向上运行还是向下运行,不负责为什么向上运行和为什么向下运行.即电梯直接调用调度器的getDirection方法,获取向上还是向下的信息,传过去的参数有所在的楼层,当前的运行方向和电梯里正在执行的请求.调度器则根据电梯的状态和调度器里的等待队列判断出电梯的运行方向,将其返回给电梯,电梯每运行一层便重新获得运行方向,如此往复,将所有的电梯请求执行完毕.
UML图如下:
关于线程如何正确结束的设计方法便是当输入结束信号时,首先向调度器发送输入完毕的信号,即将调度器的end设置为true,并关闭输入线程,而电梯线程则在每运行一层后便检查是否输入结束和调度器和电梯里是否仍存在请求,若没有请求,则关闭电梯线程.
线程间的协同和同步控制如图所示:
2.第二次作业
第二次作业仅仅是在第一次电梯的基础上增加了电梯的数量,最多有五部,并根据初始的输入电梯数目决定有多少个电梯运行,而电梯的类型等完全相同,但是电梯的载客量有了一定的限制,不能超载,所以总体来看第二次作业只需要在第一次作业的基础上进行一些调整和增加即可.
我的设计策略便是增加一个主调度器线程,在电梯的专有调度器之上,负责不断接受来自输入的请求,并决定分配给5个电梯的调度器的其中一个,因为本人比较懒惰,又想不出比较好的多电梯调度算法,便直接听天由命,使用随机数来决定分配给哪个电梯,然后让电梯自己的调度器去处理.
关于如何设置电梯的线程数目时,本人根据现实的生活中的情况,电梯不可能凭空消失也不可能凭空出现,所以在程序一开始便创建了5个电梯,但根据输入的电梯数目决定使用哪几个电梯,并将不使用哪个多电梯线程关闭,防止空跑消耗cpu时间.
UML图:
第三次作业(更细):
第三次作业的要求瞬间变得多且复杂了起来,电梯的数目可在运行中增加,并且电梯的种类还各不相同,运行时间,载客量,可停靠楼层也都不一样,这遍瞬间增加了作业的难度,但是细细想明白后作业的难度也并不是很大.电梯的数目虽然可以增加,但是最大的数目还是只有6个,所以我还是依据现实中的情况,在程序一开始便设置了6个电梯,只不过另外三部电梯还没有初始化类型与姓名,让其停止在第一层.再后续跟进输入增加电梯时,便将电梯的类型和名称设置好并投入使用.第三次的总体框架仍然和第二次作业相类似,使用主调度器和电梯的专有调度器,只不过主调度器不是随便将请求传输给哪个专有调度器,而是根据现有的可运行的电梯和请求的类型来决定分配.
因为电梯的可停靠楼层不一样,所以在输入类中我便将所有的输入请求需要换乘的全部拆分成了两个部分,并分别传给主调度器,拆分的策略是采用半动态式的拆分,根据目的楼层和起始楼层决定换乘的楼层,做到尽量经过的楼层较少.当然拆分的请求不能直接交给主调度器去执行,否则会因为时序问题导致两个相同的人同时坐不同的电梯的灵异事件.所以我将主调度器里的队列分成了两个部分,一个是一进入队列即可交付给电梯的队列,一个是拆分后的二次队列,拆分后的队列必须根据拆分前的请求是否执行完毕才能决定自己是否能够执行,这里是直接在每个电梯的专有调度器中增加了人员是否出电梯的数组,一旦电梯中的乘客出电梯,即可将对应ID的数组值设置为一,主调度器的拆分后队列在检查是否运行前判断这个数组值是否为1,若为1,则可交付给电梯执行.
关于相同类型的电梯请求分配给哪个电梯,沿用第二次作业使用随机分配的方法,在对应类型的空闲电梯中随机选择一个电梯交付请求即可.至于载客量,运行时间等则可直接在电梯类和调度器类中进行扩展即可.
UML图:
二.bug分析
1.自己debug
神奇的是,这三次作业的中测在提交的过程中并没有出现WRONG ANSWER的情况,出现的大多是CTLE 和 RTLE,而这两者基本上都是同时出现,CTLE的出现往往是新增功能时莫名使用了类似于暴力轮询的结构,所以一旦出现,便对其进行wait,notify和sleep操作.而RTLE则是因为程序没有正确的结束或出现死锁,所有程序都在wait的状况中.这种的解决办法就是严密检查判断结束变量的取值与线程安全情况,而死锁的解决办法便是直接破除,采用其他方式达到相同的目的.
当然极其伤感的是,第二次作业因为只花了2-3个小时便完成了,且中测交上去就直接过了,便满怀信心的直接pass,没想到强测栽在了载客量的限制上,因为我在判断是否超载的代码中的两个分支中只在前一个分支中进行了处理,另一个分支忘记处理了,导致了程序wa掉.
对于RTLE的分析,我都是采用IDEA的自带THREAD分析和JPROFILE共同判断的操作,可以直接看见运行过程和多线程的之间的协调和wait,run关系.
2.hack别人
因为WRONG ANSWER的构建可以采用随机生成数据的办法,且出现WRONG ANSWER的情况可能不多见,大部分人的错误都是可能出现在RTLE和CTLE上,所以我便构建了几组重复性很强的且执行性很强的数据,只坐一种电梯,且都是相同的路线,可能让线程不安全的程序出现错误.
三.总结和心得体会
完成了三次电梯作业后,我不禁感慨道,终于不用像是第一单元的作业那样反反复复重构了,添加功能迭代还是轻松了许多,只需要增加了类和稍微修改代码即可达到迭代的目的.
而我对于多线程的理解也不断地加深,线程安全尤为重要,如果不加处理的话,很可能会出现灵异事件,而且程序和线程也不能够正常的结束.当然,对于java的设计模式也在自己的实践和编程中不断地体会与掌握,最后最重要的是,学会了如何避免死锁这个大坑,再也不用你等我我等你了.