buaa oo 第二单元电梯总结
-
(1)总结分析三次作业中同步块的设置和锁的选择,并分析锁与同步快中处理语句直接的关系
我选择了LinkedBlockingQueue作为共享资源,用来存储请求,由调度器和输入线程共享,减少了我需要写的同步块的数量,而在需要使用同步块的时候,我锁的是电梯对象或者是等待队列,锁和同步块中处理语句的关系是,同步块里的处理语句在执行的时候不能被锁住同一对象的其他同步块打断,否则会产生并发错误,为保证程序在多线程下仍然可以正确执行,当同步块获取了锁的时候才可以进行处理,下面对于三次作业中同步块的设置和锁的选择进行具体分析。
第一次作业:
在输入类中,我每次往等待队列中增加请求,锁住电梯对象,同步块里这两个语句,加入请求和唤醒电梯。
synchronized (elevator) { wait.add(new Request(request)); elevator.notifyAll(); }在输入线程结束的时候,同理进行加锁操作,设置电梯状态并唤醒。
在电梯类中,我采用look算法,每次判断条件的时候和每次电梯出入人的时候,锁的是等待队列这个对象(因为在第一次作业里我的电梯不是一个线程,其实这里没有必要的,它就像是一个提线木偶,全靠调度器线程去操作电梯),而且这个等待队列对象和上面输入线程的不一样,表示调度器分配给电梯的,但是还没有进入轿厢的请求的队列。
如下:
synchronized (wait) { Iterator<Request> it = inner.iterator(); while (it.hasNext()) { Request r = it.next(); if (r.getTo() == now) { it.remove(); TimableOutput.println("OUT-" + r.getId() + "-" + now); } } }在调度器类中,我将等待队列里面的请求分给电梯,涉及到线程安全,我的所对象是电梯。
第二次作业:
这次作业在修复bug阶段我进行了大幅的调整,很多同步块和锁的设置与第一次作业有不小的出入。
输入线程中,用到同步块仅仅是为了唤醒调度器线程。
电梯线程中,有用到同步块中锁自身,来进行是否wait的判断,还有用到同步块中锁的调度器对象,来进行是否跳出run的循环的判断,在开关门的时间内,我锁住的是调度器对象,进行人员的出入。在涉及到电梯的请求队列的人员变动的时候,锁的都是调度器对象。
如下判断是否结束线程:
synchronized (dispatch) { if (innerQueue.size() == 0 && waitQueue.size() == 0 && end) { break; } }调度器线程中,整个进程是一个同步块,用调度器对象来锁,当需要分配请求给具体的电梯的时候,再锁上电梯对象。
如在分配请求给电梯的时候:
synchronized (this) { right.addPerson(r);//在判断是否跳出来和将等待队列加到电梯中用的是dispatch //right为选择的最合适的电梯 } synchronized (right) { right.notifyAll(); }第三次作业:
沿用第二次作业的模式,各种线程的同步块和锁与第二次作业一致。
-
(2)总结分析三次作业中的调度器设计,并分析调度器如何与程序中的线程进行交互
第一次作业:完成第一次作业的时候,时间仓促,没有充足的时间思考,甚至电梯都没有自己的线程只是创建了一个电梯的对象,传入调度器中,电梯对象作为一个提线木偶被调度器摆布,算是一种单方面的“交互”吧。
第二次作业:第二次作业由于是多电梯,只能把每个电梯都作为一个线程才可以完成任务,于是我的调度器里多了一个电梯队列。
Dispatch dispatch = new Dispatch(list); dispatch.setName("dispatch"); for (int i = 0; i < 3; i++) { list.add(new Elevator(6,dispatch,i + 1)); }每次从输入来了请求以后,在输入线程中,会调用调度器对象的
addPerson方法,完成输入线程和调度器线程的交互,这个方法将综合电梯的状态和请求的信息选择一个合适的电梯,并把请求分给这个电梯的等待队列里面,完成电梯和调度器的交互。第三次作业:与第二次作业保持一致。
-
(3)从功能设计与性能设计的平衡方面,分析和总结自己第三次作业架构设计的可扩展性
-
画UML类图

-
画UML协作图(sequence diagram)来展示线程之间的协作关系(别忘记主线程)作业

从功能设计方面,第三次作业的架构比较简洁明了,也容易实现,不容易出现bug,并且能够完成相应的任务,在功能上可以进行扩展,难度不会很大。从性能设计方面,考虑到性能主要取决于电梯的分配策略,这样的架构下,对于调度算法的实现也隔离开了,便于后期进行优化和升级。在功能和性能方面都有一定的兼顾,较为平衡。
-
-
(4)分析自己程序的bug
第一次作业:强测无bug,互测中被发现了一个bug,我的电梯如果停在n层,n层来了人,我的判断是否要开门建立在是否其他楼层有请求的基础上,故在这时候不会进行开门操作,造成RTLE,非常感谢这位仁兄。该bug所在类为调度器,所在的方法为run方法,当时写的时候比较混乱,很多功能。
第二次作业:强测有3个RTLE的点,是线程安全的问题,在电梯和调度器线程的交互过程中,尤其是电梯类的run方法,代码逻辑比较混乱,没有将各个功能进行分离,容易出现bug,互测没有被同屋的其他人hack,可能大家都比较忙吧。
第三次作业:强测和互测均未发现bug。
-
(5)分析自己发现别人程序bug所采用的策略
-
列出自己所采取的测试策略及有效性
黑盒测试,因为多线程代码读起来比较费时间,且不容易通过静态的阅读来发现对方可能存在的bug,我这次使用评测机对别人的代码进行争性验证。可惜没有通过这样的测试发现别人的bug,
丢人。 -
分析自己采用了什么策略来发现线程安全相关的问题
在第一次作业的时候,但是很怕因为轮询产生的CTLE,对互测屋里每个人的代码都用Jprofiler进行了检测,但是没有发现轮询,其他对于线程安全相关的问题只能交给评测机去找了。
-
分析本单元的测试策略与第一单元测试策略的差异之处
这一单元不容易出现一些特别小的失误,bug也更难找一些,且多线程的执行顺序不够确定,有些测试出来的bug也不容易复现出来。测试的时候,可以考虑用jprofiler进行分析,或者就是进行评测机广撒网地进行评测,本单元的程序执行需要一定花时间,互测的时候,还需要有一定耐心。
-
-
(6) 心得体会
-
从线程安全和层次化设计两个方面来梳理自己在本单元三次作业中获得的心得体会
第一次作业:
线程安全方面:第一次作业两个线程(调度器和输入线程),非常简洁,线程安全没有出现问题,通过第一次作业,了解了多线程编程的方法,虽然设计不够合理,但是后面在优化结构的过程中,第一次作业的架构还是用得上的。层次化设计方面:学习了消费者和生产者模式,但是没有把电梯作为线程,造成我第二次作业的时候压力山大,花费了较长的一段时间,我意识到在开始后构思的时候就要考虑后面会出现的情况,为后面的进一步扩展留足空间。还有就是,我意识到自己需要通过一个较为全面的评测机制进行检测,否则很容易出现细小到强测都发现不了的bug。
第二次作业:
线程安全方面:出现极大失误,线程不够安全,导致了RTLE的出现,当时为了通过中测,对代码进行了修改,可惜没有改到要害,对于多线程线程安全的认识不够深入,后来在bug修复阶段进行了重新的梳理,构建起来了一个线程安全的架构。层次化设计方面:这次我自我感觉层次化设计较好,各个线程的分工明确,通过调度器作为输入线程和电梯线程的枢纽,将每一个请求分发给合适的电梯,第三次的作业改动不大。第三次作业:
线程安全方面:通过第二次作业修复bug阶段的改正,线程安全没有再出现问题,在做第三次作业的时候,有一个很有意思的bug,让我找了好久,分享在这里,我的调度器接收到输入线程结束的指令之后,给各个电梯也发送了这样的讯息,如果电梯的等待队列和电梯内没有人的话,这个电梯线程就要结束,但是很有可能其他电梯需要换乘,离不开这个电梯线程,当时这个bug找了好久,极大地提高了我对于多线程找bug的能力和对线程安全的进一步理解。层次化设计方面:这次的作业在层次化设计方面与第二次无异,和第二次不同的无非是在判断能不能开门,是否要换乘的这种判断。
-

浙公网安备 33010602011771号