OO第二单元——多线程(电梯)
综述
第二单元的三次联系作业都写电梯,要求逐步提高,对于多线程的掌握也进一步加深。本次作业全部都给出了输入输出文件,也就避免了正则表达式判断输入输出是否合法的问题。
第一次作业——单部电梯(无调度)论述
第一次作业,是写一部电梯的模拟器,指导书提供了一个极为基础或者说不叫算法的算法,只需要将人送到正确楼层即可,对于时间、性能没有任何要求,但也是我们第一次尝试多线程,开始写代码时对于wait(),notify()函数并不是十分了解,导致出现了各种线程不安全,或是出现死锁的状况,代码写得也比较的复杂,架构并不是十分清晰。
性能分析
类
方法复杂度
明显可以看出run方法复杂度较高,其他方法为正常水平。
类复杂度
由于没有任何性能要求,我采取了接完一个人再去接另一个人的方法,效率极低,但可以保证正确性,建立了两个线程,一个Producer线程,负责读取输入,并放入共享队列中,另个一个Consumer线程,负责处理共享队列中的指令,并指导电梯上下移动,而在这次作业中我将电梯和Consumer写在了一起,也就是没有调度器线程,而只有生产者、消费者。
在后期,我也遇到了一些问题,比如结束程序的问题,我在Ctrl D后,并不能结束进程,而是卡在了一些死循环或是wait里,最终我也在用了信号量的情况下多加判断,结束进程。
第二次作业——单部电梯(ALS调度)论述
第二次作业,与第一次作业要求并无太大差别,只是增加了地下楼层,也就是负数楼层, 但在性能上有所要求,指导书给出了ALS算法,即捎带算法,在电梯运行过程中捎带路过楼层的乘客,相较于第一次作业的FAFS算法,有了很明显的进步,性能大大提高,难度也有所提高。输入输出接口仍然给好了jar文件,无需我们担心。下面先看一下IDEA对于我代码的分析。
类图
方法复杂度
复杂度较高的几个方法中都包含了较多的if条件判断,为确保正确性,只能以性能换正确。代码设计并不是十分到位。
类复杂度
第二次作业,我按照指导书使用了ALS算法,每到一个楼层,我会先让进程sleep(400),以度过开关门时间,然后检测电梯的等待队列中,是否有当前楼层的乘客在等待,若有则进入电梯乘客队列,接着遍历电梯乘客队列,将到达楼层为当前楼层的乘客送出电梯,每一次电梯运动前,都会更新一下当前的目标楼层,目标楼层也会综合考虑等待队列中的出发楼层,然后电梯根据目标楼层选择向上运动或向下运动,每运动一层为一个周期。
我认为这次作业的难点也就主要在调度一块,若是调度算法太差会导致Real-Time-Limited-Exceed,若是暴力轮询不sleep()会出现CPU-Time-Limited-Exceed,这也是我后面自己试出的一个小技巧,由于对于wait(),notify()两个方法的理解不到位,我的电梯内部采用暴力轮询,以防止错过任何一条指令或是进程在等待i队列还有指令的时候就结束。 但是我在获取指令的进程中采用了notify(),以唤醒get()方法中的wait()。
第二次作业我并不是十分成功,由于个人架构太失败,导致调度环节出问题,电梯运行方向并不会正确的改变,导致在强测时挂了多组数据,未能进入互测,这次作业我吸取了教训,知道一个不正确的架构,一定写不出一个正确的程序,在开始码代码前一定要考虑周全,想到程序运行的每一方面,尤其是结束进程方面,一定要在开始之初就设置好信号量,做好规划,而不能等到写完程序后在程序内部打补丁,强行结束程序。
第三次作业——三部电梯(自行调度)论述
第三次作业提高要求,设置为三部电梯,且每台电梯能够到达楼层互不相同,也就导致了需要乘坐两台电梯才能到达目标楼层的操作,且每台电梯运行速度,最大承载量也不相同,一开始我想使用继承,一个父类电梯包括电梯所有操作,然后用子类修改电梯的各种数据,造出三台电梯,但后来我发现了更加直接的方法,使用构造方法来确定电梯的几个参数,无需使用继承,且继承run方法我也不太确定是如何运行的。下面先看下IDEA的分析。
性能分析
类图
方法复杂度
复杂度高的函数如Tray.get(),因为三台电梯不同,导致获取的目标也不同,也就是用了if判断语句,增加了复杂度,但以后可以使用数组,传入index以减少if的使用,而在获取aimFloor一块,依然复杂度极高,毕竟有多种情况,我只能使用if判断,手动判断情况。
类复杂度
第三次作业我还是使用了生产者-消费者模式,由于会出现一台电梯无法送达的情况,我选择在当初将指令放入等待队列时,便拆分好,我会考虑所有的中转楼层,选取其中路线最短的中转楼层,然后将前一条指令先放入恰当的电梯中,后一条指令放在我的Monitor线程中,这是我新开的一个监视者线程,负责查看出电梯人员中,是否有id与监视者的等待队列中的id相同,若有,则该等待队列中的指令放入适当的电梯等待队列中,其他的架构都与第二次作业都差不多。
第三次作业的强侧表现也不太好,在这里我分享一下我debug的经历,我频繁出现CPU-Time-Limited-Exceed的问题,根据以往的经验判断,一定是暴力轮询没有sleep导致的,但我也并不是很清楚是哪一个while循环出现的暴力轮询,于是我才用输出debug的方法,在每一个while循环内部都都增加了输出,“XXX(类名):XXX(方法名) Here!”, 以判断哪一个while出现暴力轮询,当然输出结果极其恐怖,一个txt文件里面有25w+行的输出,显然找到了bug所在,但是自己在课下测试时并没有仔细测试。
这一次的经历也教会了我使用JProfiler,可以在Thread模块,查看是否有block,若是出现block,即代码内容有问题,然后使用输出debug,找到bug所在。