面向实际的电梯模拟? - 2021 面向对象程序设计第二单元总结
这一单元作业有一个统一主题——即多线程编程。其要求根据运行模式模拟一部或多部电梯搭载乘客从所在楼层到目标楼层的过程,且可以满足乘客在任何时间、可达楼层到达,并提供目标楼层。
我们的程序主要功能是:第一次作业为模拟一个标准ALS电梯运行,第二单元增加成多部电梯同时加入运行,第三次作业则是定义电梯的可达楼层,并可通过换乘的方式将乘客最快送达目标楼层。以上所有作业都要能够获取乘客的需求,并使用一定的策略将乘客运送到目标楼层。
由于这一个单元程序结构基本相似,所以我会进行简单陈述之后进行数据列出。
1. 程序结构
相对于上一个单元的迭代形式,这个单元我使用同一个架构完成了第五到第七次作业,即控制器响应输入线程,电梯响应控制器并完成自动运行,最后会按照自动运行逻辑执行接送人操作。之所以不设置调度器的原因是因为构建结构的时候由于奇奇怪怪的原因构建了自己的 LOOK 算法,因而自此之后我的程序可以认为一直是集中调度,电梯间自由竞争。
由于调度方式从根部来说还是隶属于集中调度,所以我在所有的对于存取 request 用的 requestMap 的增删、读取过程中都对 requestMap 加上锁进行控制,俗称读写锁。之所以没有采用写优先的锁控制是因为,读一层的请求中 Iterator 所用的时间几乎可以忽略不记,所以我经过一些小的 Demo 尝试以后采用了这样纯粹一些的设计。
下面是我的程序 UML 图:
(第五次作业)
(第六次作业)
(第七次作业)
2. 程序分析
线程分析:
由于程序是集中调度,因此我没有调度器线程,控制器也是静态的,所以只存在于简单、间接性的线程交互。
由Main启动InputThread,再由InputThread调用Controller,Controller初始化再启动初始电梯数量,并加入 Thread[] 进行简单的线程管理。电梯会读取 requestMap 并进行增加删除操作,读取到请求后就进入全自动运行过程,逻辑比较简单。
画出线程运作图是一下这样的,比较简单:
度量分析:
由于除了控制器代码有一点点改动以外,主要的代码段没有什么区别,因此我挑选了第七次作业的度量进行分析。可以看到在电梯运行的时候,我产生了臃肿的逻辑控制块,也让我的程序确确实实带来了bug。从命名的角度来看,可以看出我的程序在几乎相似操作的时候使用了复制代码的操作,可以说是绝对的禁忌,但后续几次作业由于电梯运行是自动且可靠的,因此没有进行优化。
扩展性还算可以,添加功能迭代的时候不需要很多的代码添加,但控制器的扩展性比较低,想要增加控制逻辑不是一个简单的问题,这一点还需要改进。
3. Bug 分析
由于 Bug 丛生,导致了各种各样的问题。
第一次产生bug是因为运行逻辑出错,由于没有想清楚就开始动项目,导致脑子里思考的是 调度器+LOOK 的混合算法,控制冗余,很容易出错。
第二次bug产生在 Windows 无法评测的问题上,需要 Llinux 才可以还原出评测机的输出信息,导致我研究了一整天都没有研究出来到底是哪里出现了 bug。
第三次作业bug出现在输入线程输入结束,且电梯接送的乘客没有到达目的地并需要换乘的时候,我的控制器会发出 close 的信号,并将 waiting 的电梯线程notify起来后调用Closeable接口,导致了乘客下了电梯之后无电梯可做,导致了bug。
三次bug产生都是发生于控制器的控制阶段,以及控制器的逻辑设计。此外由于为了防范死锁问题,我仅仅在对 requestMap 有可能有增删操作的方法中对 requestMap 对象进行了加锁,逻辑上也避免死锁问题,我认为这种减少对象锁的方式是稍好的,容易避免意外的死锁问题。
4. 学习心得
第二单元让我对线程与多线程,以及多线程项目的成形方式有了很深刻的了解。对于程序的设计模式,比如生产者消费者模式,事件驱动模式等等都有了一定的认识。
这其中最令人头疼的还是多线程的 debug 问题,我也没有解决的办法,和大部分同学一样,我使用 printf 大法进行手动 trace 找 bug。虽然我知道这样效率很低,但是也是仅有的解决途径。我也尝试了一下同学分享的IDEA插件,感觉也只能是协助分析吧~