面向对象第二单元学习体会与总结
-
-
基于度量工具的程序分析与评价
-
程序bug分析
-
测试分析
-
第三单元心得体会
1)第一次作业为傻瓜电梯,运行的基底标准为FAFS的调度策略。在这次作业中,由于自己是第一次接触多线程,便直接套用了《图解Java多线程设计模式》中的“生产者——消费者”模型。输入线程People与电梯线程elevator共享一个requestQueue对象。其中requestQueue以数据结构Queue为基础,通过对add与get方法进行加锁(synchronized)实现线程安全。这次作业可以说是很简单了,算是让我们初开多线程设计的大门吧。
2)第二次作业为可稍带的电梯,运行的基底标准为ALS的调度策略。我的设计是给电梯设置”Unsure”的等待状态与“Up”,”Down”的两个运动状态。在Unsure状态下,等待电梯中出现指令并立即去响应其中最近的指令,之后将电梯状态设置为与该指令所需要的相同的状态。电梯处于两个运动状态之一时,去收集并响应与当前方向相同的所有指令。在执行完毕之后再进行反方向的扫描,以此决定电梯之后是进入另一个运动状态还是进入Unsure态。第二次作业我的类构造布局与第一次大同小异,主要增加的是电梯在各个时段对请求队列的请求获取方法。这个调度策略类似于网上能查到的LOOK算法。
3)第三次作业为捎带+不同运送能力的多电梯。运行基准简单干脆地给了200s。(感觉只要程序能正常结束就应该不会超时,但线程增多也使得正常结束成为了这次作业尤为需要注意的一个坑点)我的设计是总体中有请求输入,三座电梯,总调度器这几个线程。请求输入时,由调度器判断该指令是否可以直达,若可直达,交给总调度器中负责处理直达指令的调度器;若不可,则新建立一个处理拆分指令的调度器线程并start。通过各个调度器的处理,指令被分配到各个电梯的缓冲区中。电梯的调度策略相对于第二次作业有所改变,即在任何时候都收集当前缓冲区中所有的指令。之后让电梯不断重复上行与下行,每一趟都处理完可以处理完的指令。这个算法应该就是纯正的LOOK算法(事实上第二次是希望能比LOOK做得更好,结果并没有😭)。
第五次作业:
略(实在是过于简单,我甚至有看到同学三四十行直接莽完的)
第六次作业:

总结:
Elevator类在指令执行的过程中做了过多的事情(扫描指令,获取指令,多种不同运动状态的转化。。。。),使得其显得十分冗杂,指令的获取与执行没有很好的分离。整体的设计框架从逻辑上还是比较直观的。
类图

UML时序图

代码质量分析


总结:
这次设计从类图上可以看出相较于第二次作业多了一层调度,相应的出现了大量的线程交互处理。在Elevator的执行上我自问这次做了足量的分离。但由于大量的get型方法以及线程交互相关函数使得Elevator类仍然长的一批。。。😅
3、程序Bug分析
同前,过于简单,公测与互测都没有自己出现任何bug。(实际上是自己只实现了这些了这么点功能,想错点啥都难啊)
第六次作业:
中测中,我交了8次全错的提交。。。原因是没能正确判断最终的终止指令的读取(我是在读入null后向队列中加入一个自定义的静态PersonRequest型变量end)。事实上,这次的强测我只过了两个点,直接就没进入互测,原因还是程序没能正确的停止。最后具体在程序中,我是通过队列中有且仅有end指令时结束电梯运行,结果我在一处清除当前已执行完的指令时误清了end指令。。。唉,一失足成千古恨。
第七次作业:
这次和上次的中测的数据强度不大,基本都是功能性测试。我在两次提交后,解决了拆分指令第二条还没送入电梯整个电梯系统就提前结束了的bug,过了中测,强测与互测我都未被测出bug。至于之前那个bug的解决方法,由于我是每得到一个拆分指令就会新创建一个SchedulerChange线程,我便在一个这样的线程生成时让Scheduler中的一个int型变量加一,当这个线程结束时再使这个int型变量减一,这样,当最终电梯缓冲区中的指令为空,并且这个标志变量为零是便可以安全结束电梯系统。
4、测试分析
第五次,第六次作业:
这两次作业,由于调度逻辑简单,运行过程清晰,我都是用一些自己想的数据进行很少量测试。当然,尝到了恶果。。。就是第二次作业连互测都没进。。。
第七次作业:
痛定思痛,在这次复杂度上升了很多的作业中,我采用了python调用Java程序的方法进行随机自动化测试。具体来说就是利用python中的subprocess模块开启了一个新的子进程,并让其运行作业的Java程序。
数据方面,设置一个num参数,并生成num个在0~num秒范围内的随机时间,与num个随机的用户请求相对应。得到一组长度为num的数据。用num=15测试正确性,num=40测试性能。
时间方面,用time模块定义一个初始时间。并用sleep(0.01)的方式去轮询此时已经过去的时间,一旦大于指令的投放时间,投放指令,并从数据组中去掉该数据。
正确性方面,由于时间紧迫,没能判断所有逻辑。判断的是每个乘客请求的完成情况(是否完成,换乘先后顺序是否合理等)
这样一个简陋的测试程序,测出了我的程序中的一个bug。那就是多个SchedulerChange线程对Scheduler中的标志变量的修改我没有为其上锁。。这样的后果是这个标志无法正常归零,要么电梯系统没执行完指令就停了,,,要么就停不下来。。。算是很致命的bug了,能测出来也算没白写这个测试程序。
线程安全与稳定
这一单元,让我深刻的感受到线程安全性与稳定性对结果造成的影响有多大。。第六次作业误删了end指令导致电梯进入了无限等待,,让这次作业直接完蛋。。第七次作业中由于每个换乘指令我都会新建一个线程,它们对总调度器中标志变量的修改必须同步,否则程序就无法正常结束。此外,这类线程需要在输入拆分指令的第一条后需要访问电梯线程的请求队列,在检测到指令执行完后才能向另外的电梯线程中投入下一道换乘指令。在这个过程中,由于可能会有多个线程访问同一部电梯,对请求队列的访问必须加锁。此外,若拆分指令的第一条指令未执行完,应该让SchedulerChange线程wait()(若轮询判断很可能CPU时间超时),在此,判断谁进了谁的等待队列,谁来唤醒谁,什么时候唤醒,都是必须认真斟酌的问题。总之,在设计多线程程序时,一定一定要提前认清哪些是共享变量(对象),并设置好对这些变量访问的同步。wait和notify的时机也是需要认真考虑的。
第五,六次的作业中,我采用的设计是比较标准的“生产者——消费者”模型。即输入线程与电梯线程以调度器为共享对象,请求队列也位于调度器中。这样不同部分之间的分工比较明确,但是电梯在执行指令时移动与指令执行的部分过于复杂。第七次作业中,为了解决可能连续到来的多个拆分指令,我使用了Thread-Per-Message模式,即不断建立新线程,实现了不同拆分指令同时进行投入——等待——再投入的过程。同时,我将指令的执行封装在了request的execute方法中,一定程度上减少了耦合。但在第七次作业中,由于电梯需要向其他线程传递许多信息,并响应其他线程的各种请求,原本减少的代码又增加了许多。。经过反思,我觉得应该将电梯的部分继续细分,让它由”规格“,”运行“,”交互“等几个互相独立的模块组成,使得电梯内部的分工更加明确,彼此的界限更加清晰。
调度策略分析
三次作业在电梯响应请求的策略上,我都是采用LOOK或者近似LOOK的办法。这种方法由于通过不断地上下行并每次都完成一批同类请求,在随机数据上有着较好的稳定性。
在第七次作业中还涉及到了请求分配的问题。我采用的策略是:对于能直达的请求,将能够响应的电梯按A,B,C的顺序排列后选择尽可能靠前的(不满载的情况下);对于拆分请求,通过遍历三个电梯的可达楼层去寻找一条理论时间最短的路线,将第一个请求插入第一个电梯,并在确认这条指令执行完之后插入下一条。
以上的分配是我设想的分配方案,在我自己用随机数据进行测试之后,发现A,B的工作量远远多于C,C大部分时间都处于闲置状态。。我立刻意识到这样对于任务完成的时效性是非常不好的。于是我做了以下调整:若直达指令最终给了B,但此时B有任务,且C也能执行这条指令,便转给C。拆分指令在拆分后的处理同理。这样修改之后,发现C的工作量明显增大了,缓解了任务分配的不均衡情况。但是以上都是我凭借表面呈现出来的情况做的分配与调整,我认为让电梯之间交互更加详细的信息,以此实现更佳的协作才是提升优化的正确方向。




浙公网安备 33010602011771号