OO Project2 电梯、多线程
综述:本单元要求使用多线程编程,设计满足要求的电梯。由于本人初次接触多线程编程,很多地方考虑不周,每周回顾上一周的架构之时,都觉得自己分外的蠢,可能这就是所谓进步吧。
第五次作业
本次作业的电梯性能是以ALS捎带型策略作为性能基准,与之后两次作业相比,只有一部电梯,略显简单。
设计策略
由于明确要求使用多线程,加上课上刚刚学习过的“生产者——消费者”模型,很容易想到以输入流Waitline作为“生产者”,调度器Controller作为“仓库”,电梯Elevator作为“消费者”。
可扩展性分析
关于可扩展性,当时的想法是,如果出现多部电梯,就是有多个消费者,那么只需要让消费者们每次只能同时有一位进入仓库取走Request,同时调度策略的计算,发生在每个电梯内部,每个电梯都对当前队列进行计算,得出最近的电梯即可。
事实证明,这是不可取的,电梯本身是一个线程,时刻维护自己的状态已经很繁琐,再加上时刻维护一个调度策略算出的自己与目标指令的契合度更是困难。更甚者,要获得较优的调度,就要综合考虑其他电梯,将考虑多个线程综合才能得出的方法,放在每个线程中,混乱!具体方式在第六次作业中给出。
程序架构度量分析
啥也不说,先上架构图:
总体来说,还是比较简单的,没有什么可以过多解释的,标准的“生产者——消费者”模型。
复杂度上看也无不妥,run方法复杂也是难免。in方法写麻烦了,没有发现,后边一直在沿用。。。调度策略是标准的LOOK算法,不过是自己想出来的,没有借鉴任何网络内容,于是冗余的参数和函数其实也是有的。
Bug
由于第一次接触多线程,不知何为“暴力轮询”,在没有指令在等待队列中时,便会陷入while循环,CPU空转,导致CTLE的产生,在添加一个wait()和一个notifyAll()后修复。
发现别人Bug
放进测试机,跑就完了——>难以复现,可恶——>不测了,测出来也hack不到。(于是后边也都没有hack)
————————————————————————————————————————————————————————
第六次作业
第六次作业惨了,现在想想,很多方面都有问题,不只是当时觉得的“模式问题”,是最惨,bug最多,分最低,也是收获最多的一次作业。
设计策略
仍试图采用“生产者——消费者”模式,注意了“暴力轮询问题”,沿用第五次作业的LOOK算法的电梯,在电梯中计算每条指令和自己的“预期距离”来显示此电梯和每条指令的契合度,契合度高的电梯获取指令,写出了version0.0的程序,后发现电梯很容易争夺相同指令时,获取空请求,产生bug,甚至提前结束线程。在经过几个版本(至version0.3)的debug无果后,分析后认为电梯任务过重,且在一个电梯线程中是不应该获取其他电梯线程的信息的,调度应由控制器完成。
version1.0,直接通过了弱测和中测,控制器Controller维护指令队列,将指令按一定的算法一条条“塞进电梯”,电梯只负责执行内部的指令队列。现在看来,思路尚可,但实现时,有以下几个失误点:
(1)算法部分很复杂,将它放在了Controller中,使得Controller的控制功能和计算功能混杂,难于debug
(2)将Controller作为一个线程,并作为其他线程的锁来使用是十分不安全的:
(i)Controller线程先退出:Controller线程退出后,若仍有电梯运行,将无法唤醒电梯,虽不会CTLE,但电梯线程无法终止;
(ii)电梯先退出:Controller需要轮询获取电梯是否全部退出的状态,若设锁,在两个线程互相用锁时极容易死锁。
不如使用普通的Controller对象,退出作为一个方法,便只需考虑电梯退出。实现时,设锁控制,锁均为Controller对象,避免死锁和轮询。
(3)电梯直接沿用了第五次作业,电梯作为一个消费者,获取指令的动作是“取”,而这里的获取指令是Controller“塞”,但没有仔细思考,直接使用了Elevator的get方法,导致线程不安全。
version2.0,version1.0在强测中有4个bug,hack中有2个bug,出现了CTLE、RTLE、RE三种错误,原因就是上述三个失误点造成的,进行了重构修复,Controller不再是线程,算法内置在了Controller的内部Caculator对象中,电梯重新考虑了synchronized控制块。
可扩展性分析
这次的version1.0无扩展性可言,算法与控制混杂,电梯同步控制问题,线程安全问题都存在着,是很不好的设计。
version2.0扩展性好,改变算法只需修改Caculator模块,改变电梯属性只需修改Elevator,Controller只有简单的指令进入(add)、指令分配入电梯(pushToElevator)、暂停(wait0)、唤醒(notice)和关闭(close)几个必要的控制方法。
程序架构度量分析
直接展示了version2.0的:
下面是复杂度分析:
难免有一些方法需要多重遍历。。。难免。。。总体来说还能看吧
Bug
复现不出来,原因在设计策略中分析过了,不再赘述,直接重构进行了修复。没有hack他人。
第七次作业
沿用第六次作业的version2.0,算法采用了多种的混合算法。
设计策略
由于电梯种类增多,将Elevator作为抽象类,增加ElevatorFactory工厂模式获取电梯,电梯A、B、C继承Elevator,并有略微不同。
线程控制仍使用第六次作业的version2.0。
算法较为复杂:
1.直达:区分为A、B、C类型,可同时满足ABC的两种以上,用random分配成具体类型的电梯(A/B/C),在当前类型电梯中寻找距离最近的电梯并加入。
2.换乘:根据最短路径分为A、B、C类型请求,根据STAF算法加入应当加入的电梯。
3.电梯自身采用了LOOK算法。
可扩展性分析
较高,算法改变变Caculator,控制改变改Controller,电梯属性改变改Elevator,每个模块分工明确。
程序架构度量分析
看起来复杂,其实ElevatorA、B、C差别很小。
由于电梯A、B、C的代码基本一致,图片过于长,就裁去了ElevatorA、B类的部分。容易看出复杂的部分在Cauculator中,这是正常现象,毕竟得出最优调度有很多循环遍历,也有很多要考虑的复杂计算。
Bug
没有被hack出bug,强测无bug。
可拓展性分析综述
可以添加关闭电梯指令,突然中止电梯,模拟现实中电梯突然坏掉的情景,中止后有维修工在一定时间后到达,到达后其中的乘客在当前楼层被放出,被迫换乘。(我真的不是想造福学弟学妹)
最后看看几个原则:
单一责任原则(SRP):像最后一次作业,读取、算法、控制、执行分别交给Inputer、Caculator、Controller、Elevator,很好的体现了单一责任原则,像第六次的version1.0就把算法和控制混合,最后控制出错难以调试的情形,就是没有注意SRP原则导致的。
开放封闭原则(OCP):以我当前水平不太可能做到不去修改一个类的方法内容,只进行增加来达成新的要求,有待练习。
接口隔离原则(ISP):没有使用接口。
里氏替换原则(LSP):这是我要细说的,在作业结束后仍需改进也有能力改进的问题,就是虽然不同型号的电梯进行了继承,但由于Elevator也继承自Thread类,导致许多代码不知如何去写,由于时间所限,没有时间再去学习多重继承的有关内容,于是便将A的代码复制到B和C进行一些修改,虽然结果正确,但代码行数增了500,相信学习多重继承有关内容可以解决这个问题。
依赖倒置原则(DIP):工厂模式解决电梯类的创建,不同型号电梯继承自Elevator抽象类。
心得体会
多线程很难写,调试很复杂,甚至需要借助其他软件。
多线程很容易出错,且难以复现,bug出了很多,但确实收获也很多。
总之结局还算圆满,最后一次的作业终于全过不用debug。
其实多线程在用不着经常线程交互的时候编出来还挺好使的。
回过神来,这一个月,我们学到了“生产者——消费者”、“观察者——被观察者”线程交互模式、“死锁”等线程安全问题和解决方式,都是自己从来没有接触过的新模式、新问题。电梯调度算法也仍是一个值得持续思考的问题,我的算法虽然性能分不错,但仍有改进的余地。
加油!奥力给!