第一次作业
第一次作业要求我们完成一台电梯简单的傻瓜调度,题目比较简单,主要让我们熟悉多线程的操作。
这次作业虽然说使用单线程也可以解决,但为了以后的复用,我使用了多线程,一个读入请求线程和一个电梯操作线程,读入请求线程放在主类里,此外还创了一个请求类,里面存有三个量,分别是id,from和to的信息。电梯调度使用作业中所述的傻瓜调度,先请求先执行而且中间不加捎带。吃请求队列时使用轮训,sleep睡一会儿后判断是否有新请求。电梯操作内部,因为不需要捎带因此只需要判断电梯升降开关门就行了,而且因为不需要输出每一楼的信息,因此可以直接用初始楼层与最终楼层的差算出等待的时间,我这里使用的每一层等待一会的方法,虽然打的代码多了一点但是便于以后的使用。
(1)基于度量来分析自己的程序结构
本次作业从度量上来看还是可以的,除了电梯运行模块没有太长的地方,尽可能做到每个方法进行封装。不足的地方是输入请求没有单独开一个线程类,而是直接放在主类里了,虽然对于后面几次作业都没有影响,但是从面向对象的角度来看并不合适。
UML协作图
主类负责读取输入且给Input变成一个含有三个信息的类,在主类里变成一个动态数组,Lift中有一个动态数组不断去吃这个数组里的内容,并且进行电梯操作。
Main类做了输入操作,不符合SOLID。
类图
优点:看起来真的简单
缺点:输入没有开单独的线程类
(2)分析自己程序的bug
自己测试自己时,主要手打一些边界数据进行测试。
由于这次的代码比较简单,因此第一次并没有使用评测机去测试,也就在java下放的run窗口放了一些数据去测。
线程安全并没有发现问题。
(3)分析自己发现别人程序bug所采用的策略
这一次作业因为比较简单,大家似乎也没有什么错误,我在乱扔了几个数据之后就没有继续进行测试,而最后的结果也是我这个组没有人存在bug。
第二次作业
第二次作业在第一次作业的基础上需要我们进行捎带,而且有了性能分的存在,指导书上的方法是同向捎带,而这样的方式并不能做到性能上的优化。
因此,我所采用的方法是LOOK算法,即每次判断所要带的最高楼层和最低楼层。这里详细说一下我的思路,我有一个上下行的变量,初始时设为下行,此时判断from最低楼层是几楼(此时需要为上行的,即to>from),如果大于等于1则改为上行,否则为下行,同理当上行送完之后我继续判断下行的最高from楼层,判断完最高楼层之后去接他。而在这个过程(即去接最高和最低楼层人)的同时,如果有人顺道(即from为当前楼层且to的方向与电梯方向一致)则捎带他,判断to的楼层是否变成最高低的楼层并更新,然后开始进入最高或低的楼层向上或下的接人送人的捎带过程。这里还需说明,因为这次的电梯没有设置容量因此,我会在楼层等于from把这个人接到电梯,可以节省一定的时间(虽然很不科学)。总的来说,就是找最高抵楼层去的时候捎带,走的时候也捎带,而且能进的一股脑都进去。
依旧如第一次作业一样开三个类两个线程。
(1)基于度量来分析自己的程序结构
看起来每个方法也不是很多呀,只是这个名字起得有点随便,可能也就我自己能知道在干啥。总的来说,代码挺短的,里面也没有什么复杂操作,基本都在电梯里实现,主要分成去和送两个部分,虽然这里面有交叉。当然,老毛病就是输入没有开单独的线程类。
UML协作图
主类负责读取输入且给Input变成一个含有三个信息的类,在主类里变成一个动态数组,Lift中有一个动态数组不断去吃这个数组里的内容,并且进行电梯操作。
Main类做了输入操作,不符合SOLID。
类图
优点:看起来真的简单
缺点:输入没有开单独的线程类
(2)分析自己程序的bug
评测机在快结束时才完成,因此测试自己的时候基本就是手测,也就存在着一些bug。
这次的bug主要是在判断最高低层楼层上下行转换时有点小问题,导致转向出错,而如果正好发生在-3或者16楼时,会先运行一个楼层也就是到-4或者17楼从而炸了,后来在debug时理清了一下思路改成正确的转向。
线程安全并没有发现问题。
(3)分析自己发现别人程序bug所采用的策略
使用自己打的评测机进行测试,但是判断是否出错的时候只是按照是不是所有人都进入和出来了,因此在我所处的room中没有发现其他人的错误,可能也是因为A组本来错误就少吧。结束时,整个room只有我一个人被hack。
第三次作业
第三次作业终于开始变成一个复杂的多电梯了,虽然这几个电梯有点奇葩,各种属性都不同而且还存在固定楼层停靠,采用的调度指导书所给的依然是基本捎带。
这次作业终于难得的可以继承之前打的代码了,因为懒得使用继承所以直接使用第二次的电梯线程复制了三次更改里面的参数,并做了一些对于这个题目的修改。比如不会一股脑的全部进人了,毕竟这次电梯容量有限。此外,开了一个调度器线程进行分配。我的分配思路是如果A电梯能单独送且容量不炸时A送,B能单独送且容量不炸时B送,C同理,如果要换乘则进入对应需要先进入的电梯,等其出来时初始楼层变成送达楼层,最终楼层还是原来的,即产生一个新请求,要求第二次运送,因此我的电梯线程与调度器之间是有一些变量交换的,因为要照顾线程安全,因此我主要使用Arraylist.add的操作并且对于不同线程对于同一个变量的修改我采用开一个锁住的方法,统一在这个方法中操作。
(1)基于度量来分析自己的程序结构
Method | BRANCH | CONTROL | LOC |
Input.Input() | 0 | 0 | 2 |
Input.Input(PersonRequest) | 0 | 0 | 5 |
Input.getfrom() | 0 | 0 | 3 |
Input.getid() | 0 | 0 | 3 |
Input.getto() | 0 | 0 | 3 |
Input.setfrom(int) | 0 | 0 | 3 |
Input.setto(int) | 0 | 0 | 3 |
LiftA.LiftA() | 0 | 0 | 2 |
LiftA.Sleep(int) | 0 | 1 | 7 |
LiftA.close() | 0 | 1 | 7 |
LiftA.firstprocess() | 0 | 11 | 40 |
LiftA.get() | 0 | 5 | 29 |
LiftA.getOver() | 0 | 0 | 3 |
LiftA.geta() | 0 | 0 | 3 |
LiftA.getah() | 0 | 0 | 3 |
LiftA.getai(int) | 0 | 0 | 3 |
LiftA.in(int) | 0 | 2 | 11 |
LiftA.liftadd() | 0 | 1 | 5 |
LiftA.liftprocess() | 0 | 3 | 20 |
LiftA.liftsub() | 0 | 1 | 5 |
LiftA.out(int) | 0 | 2 | 11 |
LiftA.process1(int) | 0 | 7 | 37 |
LiftA.process2() | 0 | 6 | 30 |
LiftA.process3() | 0 | 9 | 29 |
LiftA.process4(int) | 0 | 1 | 9 |
LiftA.run() | 2 | 13 | 65 |
LiftB.LiftB() | 0 | 0 | 2 |
LiftB.Sleep(int) | 0 | 1 | 7 |
LiftB.close() | 0 | 1 | 7 |
LiftB.firstprocess() | 0 | 11 | 40 |
LiftB.get() | 0 | 5 | 29 |
LiftB.getOver() | 0 | 0 | 3 |
LiftB.getb() | 0 | 0 | 3 |
LiftB.getbh() | 0 | 0 | 3 |
LiftB.getbi(int) | 0 | 0 | 3 |
LiftB.in(int) | 0 | 2 | 11 |
LiftB.liftadd() | 0 | 1 | 5 |
LiftB.liftprocess() | 0 | 3 | 20 |
LiftB.liftsub() | 0 | 1 | 5 |
LiftB.out(int) | 0 | 2 | 11 |
LiftB.process1(int) | 0 | 7 | 37 |
LiftB.process2() | 0 | 6 | 30 |
LiftB.process3() | 0 | 9 | 29 |
LiftB.process4(int) | 0 | 1 | 9 |
LiftB.run() | 2 | 13 | 65 |
LiftC.LiftC() | 0 | 0 | 2 |
LiftC.Sleep(int) | 0 | 1 | 7 |
LiftC.close() | 0 | 1 | 7 |
LiftC.firstprocess() | 0 | 11 | 40 |
LiftC.get() | 0 | 5 | 29 |
LiftC.getOver() | 0 | 0 | 3 |
LiftC.getc() | 0 | 0 | 3 |
LiftC.getch() | 0 | 0 | 3 |
LiftC.getci(int) | 0 | 0 | 3 |
LiftC.in(int) | 0 | 2 | 11 |
LiftC.liftadd() | 0 | 1 | 5 |
LiftC.liftprocess() | 0 | 3 | 20 |
LiftC.liftsub() | 0 | 1 | 5 |
LiftC.out(int) | 0 | 2 | 11 |
LiftC.process1(int) | 0 | 7 | 37 |
LiftC.process2() | 0 | 6 | 30 |
LiftC.process3() | 0 | 9 | 29 |
LiftC.process4(int) | 0 | 1 | 9 |
LiftC.run() | 2 | 13 | 65 |
Main.getFlag() | 0 | 0 | 3 |
Main.getarray() | 0 | 0 | 3 |
Main.getarrayi(int) | 0 | 0 | 3 |
Main.main(String[]) | 0 | 2 | 23 |
Task.Sleep(int) | 0 | 1 | 7 |
Task.Task() | 0 | 0 | 2 |
Task.get(int,Input,int) | 0 | 6 | 17 |
Task.getarraya() | 0 | 0 | 3 |
Task.getarrayaflag() | 0 | 0 | 3 |
Task.getarrayaflagi(int) | 0 | 0 | 3 |
Task.getarrayai(int) | 0 | 0 | 3 |
Task.getarrayb() | 0 | 0 | 3 |
Task.getarraybflag() | 0 | 0 | 3 |
Task.getarraybflagi(int) | 0 | 0 | 3 |
Task.getarraybi(int) | 0 | 0 | 3 |
Task.getarrayc() | 0 | 0 | 3 |
Task.getarraycflag() | 0 | 0 | 3 |
Task.getarraycflagi(int) | 0 | 0 | 3 |
Task.getarrayci(int) | 0 | 0 | 3 |
Task.print(int,int,char,int) | 0 | 5 | 14 |
Task.process(int,int,int,int,int) | 0 | 10 | 50 |
Task.run() | 0 | 4 | 28 |
Total | 6 | 217 | 1165 |
Average | 0.069767 | 2.523256 | 13.54651 |
其实真实有用的代码长度并不是很长,主要三个电梯复制导致代码有了很多冗余的地方。大概有用的代码量在五六百行。每个类的分工明确,且有效,总体而言还是可以的。
UML协作图
这次与上两次不同的地方在于多了调度器,主线程的请求先进入调度器进行分配再进入不通电梯,且电梯可能对于调度器有一些反馈操作(即换乘时加入新请求)。
Main类做了输入操作,不符合SOLID。这里存在一个疑问,即按我的方法我需要电梯换乘需要给调度器发送一个请求然而按照SOLID似乎电梯不应该干这个事。
类图
优点:每一块条理清晰,便于查找和修改
缺点:多个相似的类没有使用继承而是复制
(2)分析自己程序的bug
使用评测机进行自我测试,后来改了一点性能,结果在最后还剩50分钟测出自己有bug在截止前没有改完(哭泣)
错误在于对于一些A,B,C能够自己送达的请求,因为之前可能是容量不够而进入下一个if,此时并不属于不能一个电梯单独送达,而我这是把它当做需要换成进入电梯,导致其初始楼层和到达楼层出现一致(因为我在换乘时会把to变成换乘所需到达楼层),导致请求不会进入电梯,同时请求也不会被送出去。(强测50,哭泣)
线程安全并没有发现问题。
(3)分析自己发现别人程序bug所采用的策略
使用评测机狂轰滥炸,本次对于评测机进行一系列修改,使其能够判断对应id送达楼层是否正确以及电梯打开楼层是否正确。
c组刀人真的爽(然而并不想来),每次都是AOE伤害,每个人刀了几次之后就把这个人注释掉,即不评测这个人(有些人会有死循环而我的评测机无法跳出),尽可能希望多赚点分吧。
心得与体会
了解了多线程的基本操作,且对于电梯调度有了一定认识,但是没有使用wait和notify,对于这边的理解比较薄弱,需要看其他人的代码进行弥补。
对于线程安全有了自己的认识,我现在所采用的避免线程安全的方法是不同线程对于同一个量进程修改时设置一个方法并标记每种操作对应的代码,即方法输入flag指向不同操作,并对于这个方法进行加锁,让每次只能一个线程去调用这个方法,虽然效率有点低,但是从根本上解决了线程对于变量修改的安全问题。
评测机大法真的好,可以有效的测出自己和他人的bug,虽然没有改完自己的bug导致第三次强测炸了,但是通过写评测机可以也锻炼了自己的写python代码的能力,以及加强了对于评测系统的理解。