302
社区成员
发帖
与我相关
我的任务
分享三次作业中,我仅使用了synchronized关键字设置了同步块。在第二次作业中学习到的读写锁其实是一种很好的优化方式,但在我的代码中很少出现大量的同时读操作,因此并没有选择读写锁。锁的对象是同步块中修改需要保护的对象,在第一次作业中我花了很长时间理解synchronized到底锁的是什么,在第二次迭代中才有了较为清晰的理解,回头再看第一次作业中有的synchronized实在是让人啼笑皆非,但由于第一次作业复杂度较低,冗余的关键字也不会导致严重的后果。
三次作业中锁的对象也经历了改变,从对象主要为请求、等待队列到电梯状态、电梯列表等待随着锁的设计越来越复杂,不可避免地出现了很多死锁等问题,在解决过程中也更好理解锁的使用禁忌。
三次中,我将调度作为一个线程,对输入线程的PersonRequest队列进行处理。在第二次的设计中调度器产生了很严重的问题,导致了强测进入c房,但最终调度器是根据电梯的状态和位置进行的调度。我优先将请求分配给楼层较近的等待人数不超过容量的电梯,如果没有则随机分配,如果电梯正在reset,也可以将请求放在等待队列的缓冲队列,在reset结束后进行receive。第三次作业由于双轿厢电梯更加省电,则有双轿厢电梯时不考虑电梯位置直接分配,也减少了线程间的数据共享。而强测中出现的问题是因为统一处理了reset指令,由于PersonRequest的影响,可能导致电梯无法及时响应,也反映了我本地测试不够充分。最终将ResetRequest直接交给电梯进行读取,响应更加迅速,调度器的设计主要集中在第二次作业,第一次没有涉及,第三次改动较小。

我的第一二次架构并没有产生较大的变化,DoubleElevator、ABElevator和ABStrategy是第三次新添加的类,用于解决双轿厢电梯的问题。在我的设计中将Schedule和normalElevator(A/BElevator)作为线程,由Input线程得到的ElevatorRequest和PersonRequest分别传入Elevator和Schedule线程,Elevator查询是否有重置指令,并获取Schedule分配的PersonRequest。在电梯重置时,如果是普通的reset,被分配的PersonRequest放入preWaitQueue队列中,等到重置结束再统一RECEIVE,如果是DCErest,则也将请求放入preWaitQueue中,同时新建一个双轿厢电梯,在修改电梯的HashMap时进行保护。但总的来看,这其实并不是很符合“高内聚,低耦合”的思想,但由于时间紧张,没有时间再修改,归根结底还是可扩展性的问题,由于前期准备不够充分,可扩展性受到制约,无法优雅的完成新的请求。如果还有些一次迭代任务的话,可能一定要进行重构才能解决。事实上在第三次debug的时候已经深受不良架构的影响,经常出现牵一发而动全身,低内聚,高耦合导致死锁问题会因为更改而出现在不同的位置,在架构上踩的雷在下次作业中一定多多注意。

在我的架构中,输入线程和调度线程共享PersonRequset队列,输入线程和电梯线程你共享ElevatorRequest队列,同时主线程和电梯线程共享了ElevMap,如果能采用课上的线程池应该可以降低一些耦合度。其中主要问题是线程之间结束条件的传递,有时reset后返回的PersonRequest还没有返回Schedule时Schedule线程就已经结束了,因此在设计时就要考虑到线程结束的不同情况,这是线程协作中很重要的部分,其次就是锁的使用,前面已经提到。
在第一次和第二次中,电梯的调度策略相对稳定,我均采用了look策略,这个策略在三次作业中都有没有进行较大的修改,此外二三次作业中调度策略也没有发生较大的变化,我采用了距离较近和人数没满优先,有DCE时优先分配给DCE,都不满足的话随机分配。这些都是三次作业中稳定的内容,而二次中分配策略发生改变,新增reset请求许需要仔细考虑避免未能及时响应的问题,第三次作业新增多样rese,这些都会增加电梯的功能。
我在双轿厢电梯中设置了isTransformer变量,电梯在抵达中间层前会进行读取和赋值,退出中间层也会对该变量进行修改,变量由A、B电梯两个线程共享。
第一次作业比较简单,修正了一些轮询问题。但在第二次作业中,我的程序出现了严重的问题,由于轻视了问题出现的各种可能,通过中测试时没有对代码进行充分的测试,忽略了reset需要及时响应的问题。我将ResetRequest和PersonRequest一同分配,很多情况下都会出现reset被阻塞或reset延迟到达的问题,在强测中6个测试点出现问题,留下了惨痛的教训。在bug修复中对于Schedule进行了重构,把第二次作业中的问题拖到了下一周,压缩了第三次作业的时间。
第三次作业中,我出现了死锁的问题,线程共享数据中大量的锁和终止条件的设置出现了读入了在某个队列移除但未新加入的中间态结果而提前结束的问题,但修改的过程中微小的改动都会造成新的地方出现问题,架构初期考虑不不周导致了后面错误连篇。
在debug时,我通过在run方法中加入语句来发现轮询的问题,在线程结束时输出标志检查哪些线程没有结束,通过输出检查自己的中间状态。同时还可以构造一些边缘数据点,多次检查代码有没有死锁的问题。
在本次作业中,深刻的认识到层次化设计的重要性。好的架构在遇到问题时只需要根据单一职责对局部内容进行修改,不会出现出走一颗积木就整盘崩塌的问题。而在面向对象的过程中,我们需要对每个对象的职责进行拆分,比如reset不应该是一条普通的顺序响应指令,而是一个电梯的命令,在考虑到不同需求的层次时才能更好的设计。而如果能考虑到未来可能增加的功能,可扩展性也能得到保证。
线程安全则更为重要,如何能在最小同步块中保障功能的实现,在同步块外,读和写之间可能存在的状态改变,在这些都是我在完成之处考虑的并不周到的问题。如果现在问我能不能设计出线程安全的程序,我可能比最初更有信心一些,但也没有完备的思考方法,可能还是需要更多的练习以及充分的思考时间。
过去的三周十分狼狈,相比于第一单元只需要理解递归下降法的使用,多线程的复杂在每周学习就占据了大多的时间,可能在我已经开始写后对wait、notify、synchronized的理解还没有做到,有时和别人交流才能发现问题,由于时间紧张,架构也无法充分考虑,往往只是尽量满足本次作业的需求。以至于第三次作业架构十分面目可憎,周五提交了一版通过中测后很努力地想要改善测试中出现的死锁的问题,但直到周六晚上每一版都会出现新的问题,最后压线提交了最初的通过版本,万般无奈与纠结,侥幸通过了强测。收获的话,其实还是很多的,更深刻地认识到了优秀的架构的优势,至于最后差强人意的结果,还是希望课程组能给每一次作业更充足的时间。当前一次作业出现问题时,就算周二bug修复开始就完成了修复,新一次作业的时间也被缩短,作为一个初学者,难以避免的会出现一些问题,如果无法修复,在接下来的迭代中可能也会难以继续,在了解不够充分时,由于时间的限制匆忙开始,最后架构也难以令人满意。我也会注意在下一次作业中更加严谨地思考作业的架构,在迭代中更加顺利。