OO_Unit2总结

OO_Unit2总结

 

(1) 多线程协同控制设计策略

总体信号通讯策略

本单元作业,我采用的是生产者-消费者模式加类观察者模式。

通过分析指导书给出的需求,我将最终我要实现的程序简化为了“输入-调度器-电梯”,输入线程向调度器里输入请求,调度器保存请求并根据电梯的状态响应电梯的索要请求,电梯运行时,在合适的时候向调度器索要请求。

而类观察者模式,则是指电梯和乘客之间的互动方式,电梯在合适的时候向乘客发出信号,乘客收到信号之后,自行选择是否离开电梯。之所以说是类观察者模式而非观察者模式,是因为在设计时的理念是类似于观察者模式的,但实现时没有严格遵循观察者模式的方式,而是以一种耦合度较高的形式出现在了我的代码里:电梯直接遍历乘客的ArrayList,通过判断其是否符合条件选择是否使乘客离开。

输入线程与调度器的通讯策略

两者之间通过ArrayList完成,对读写操作加锁保证线程安全。

电梯线程与调度器之间的通讯策略

ElevatorThread持有Scheduler和对应的Elevator(存放电梯运行时的信息),Scheduler持有对应的ELevator,电梯线程和调度器之间的通讯包括电梯线程直接调用调度器方法的直接通讯和通过Elevator的间接通讯。好处是几乎在任何时候都可以调用相应的方法达到通讯的目的,并且相应的逻辑关系非常符合直觉,坏处是耦合十分严重,电梯线程和调度器和电梯之间出现了循环依赖,锁的粒度过粗,因为要应对三者的操作。

 

(2)第三次作业架构设计可拓展性

可拓展性还行第一次->第二次->第三次,我都没有对第一次的架构做出巨大的修改,基本都是非常顺利地拓展第一次的架构。功能设计上,总调度-分调度-电梯的三级结构使得我可以顺利将任务分段,但是这样开弓没有回头箭,损失了一部分的性能。而这样的架构也使我可以在新增需求时快速分解需求,分配给三级结构中的每一级,从而实现拓展。
S 尚可 输入线程负责输入,电梯线程负责电梯运行,电梯类保存电梯状态,调度器负责调度请求。调度器分为总调度器和分调度器,总调度器负责给各个分调度器分配请求,分调度器负责给电梯调度请求。职责明确。但是输入线程额外地承担了启动电梯线程的任务,电梯类额外地承担了通知乘客离开的任务,这点不太好。
O 不好 从第二次到第三次,增加新的需求带来的就是我对已有类的修改,没有做到开闭原则。
L 没有子类怎么会不满足LSP呢((((
I 没有接口
D 依赖实例而不是依赖抽象,在我的ElevatorThread、Scheduler、Elevator里轮番上演

 

(3)基于度量分析程序结构

第一次作业

oo_u2hw1(default package)ElevatorgetPassengers310
oo_u2hw1 (default package) Elevator isEmpty 3 1 0
oo_u2hw1 (default package) Elevator getState 3 1 0
oo_u2hw1 (default package) Elevator setState 3 1 1
oo_u2hw1 (default package) Elevator getFloor 3 1 0
oo_u2hw1 (default package) Elevator setFloor 3 1 1
oo_u2hw1 (default package) Elevator fresh 36 11 1
oo_u2hw1 (default package) Elevator move 8 3 0
oo_u2hw1 (default package) Elevator notifyAllPassengers 10 3 0
oo_u2hw1 (default package) ElevatorThread ElevatorThread 4 1 2
oo_u2hw1 (default package) ElevatorThread run 37 5 0
oo_u2hw1 (default package) InputThread InputThread 3 1 1
oo_u2hw1 (default package) InputThread run 19 3 0
oo_u2hw1 (default package) MainClass main 9 1 1
oo_u2hw1 (default package) Scheduler Scheduler 3 1 1
oo_u2hw1 (default package) Scheduler put 4 1 1
oo_u2hw1 (default package) Scheduler getMainRequest 35 8 0
oo_u2hw1 (default package) Scheduler judgeOpen 19 5 0
oo_u2hw1 (default package) Scheduler isEnd 4 1 0
oo_u2hw1 (default package) Scheduler setEnd 4 1 0
oo_u2hw1 (default package) Scheduler isEmpty 3 1 0
oo_u2hw1 (default package) Scheduler notifyAllPassengers 12 3 0




从类图中可以看到,MainClass仅起到启动各个线程的作用,之后所有事情都靠其他线程完成。InputThread管理输入并将Request放入Scheduler中,ElevatorThread负责模拟电梯的运行,电梯运行过程中会改变电梯状态,通知电梯中人离开和调度器中人进来。总体来说结构清晰,各个类职责明确。美中不足是fresh方法和getMainRequest方法有些复杂,其中一个是改变电梯状态,另一个是决定当前的主请求,较为复杂是意料之中的。

第二次作业

Project NamePackage NameType NameMethodNameLOCCCPC
oo_u2hw2 (default package) Elevator Elevator 3 1 1
oo_u2hw2 (default package) Elevator getPassengers 3 1 0
oo_u2hw2 (default package) Elevator isEmpty 3 1 0
oo_u2hw2 (default package) Elevator isFull 3 1 0
oo_u2hw2 (default package) Elevator getState 3 1 0
oo_u2hw2 (default package) Elevator setState 3 1 1
oo_u2hw2 (default package) Elevator getFloor 3 1 0
oo_u2hw2 (default package) Elevator getRealFloor 6 2 0
oo_u2hw2 (default package) Elevator setFloor 3 1 1
oo_u2hw2 (default package) Elevator getName 3 1 0
oo_u2hw2 (default package) Elevator fresh 36 11 1
oo_u2hw2 (default package) Elevator move 8 3 0
oo_u2hw2 (default package) Elevator notifyAllPassengers 13 3 0
oo_u2hw2 (default package) ElevatorThread ElevatorThread 5 1 3
oo_u2hw2 (default package) ElevatorThread run 46 8 0
oo_u2hw2 (default package) InputThread InputThread 3 1 1
oo_u2hw2 (default package) InputThread run 28 4 0
oo_u2hw2 (default package) MainClass main 6 1 1
oo_u2hw2 (default package) ScheduleAll addScheduler 3 1 1
oo_u2hw2 (default package) ScheduleAll addElevator 3 1 1
oo_u2hw2 (default package) ScheduleAll put 3 1 1
oo_u2hw2 (default package) ScheduleAll setEnd 3 1 0
oo_u2hw2 (default package) ScheduleAll schedule 32 8 0
oo_u2hw2 (default package) ScheduleAll askFor 18 5 1
oo_u2hw2 (default package) ScheduleAll startAll 5 2 0
oo_u2hw2 (default package) Scheduler Scheduler 3 1 1
oo_u2hw2 (default package) Scheduler put 4 1 1
oo_u2hw2 (default package) Scheduler getRequests 3 1 0
oo_u2hw2 (default package) Scheduler getMainRequest 35 8 0
oo_u2hw2 (default package) Scheduler judgeOpen 19 5 0
oo_u2hw2 (default package) Scheduler getElevator 3 1 0
oo_u2hw2 (default package) Scheduler isEnd 4 1 0
oo_u2hw2 (default package) Scheduler setEnd 4 1 0
oo_u2hw2 (default package) Scheduler isEmpty 3 1 0
oo_u2hw2 (default package) Scheduler isFull 3 1 0
oo_u2hw2 (default package) Scheduler notifyAllPassengers 12 3 0

从类图中可以看到,这次的结构和上次的结构大致相同,除了中间多了一个ScheduleAll进行总调度,自此我的程序形成了总调度-分调度-电梯运行的三层结构,输入线程将输入传给总调度器,总调度器调度请求给分调度器,分调度器调度请求决定电梯的运行情况,电梯的运行由电梯线程模拟,电梯的状态由电梯类保存。总体来说,继承自第一次作业,复杂度方面和第一次近似,并且留出了一定的扩展空间。

第三次作业

Project NamePackage NameType NameMethodNameLOCCCPC
oo_u2hw3 (default package) Elevator Elevator 5 1 3
oo_u2hw3 (default package) Elevator getPassengers 3 1 0
oo_u2hw3 (default package) Elevator isEmpty 3 1 0
oo_u2hw3 (default package) Elevator isFull 9 3 0
oo_u2hw3 (default package) Elevator getState 3 1 0
oo_u2hw3 (default package) Elevator getFloor 3 1 0
oo_u2hw3 (default package) Elevator getRealFloor 6 2 0
oo_u2hw3 (default package) Elevator getName 3 1 0
oo_u2hw3 (default package) Elevator getType 3 1 0
oo_u2hw3 (default package) Elevator getSpeed 9 3 0
oo_u2hw3 (default package) Elevator fresh 36 11 1
oo_u2hw3 (default package) Elevator move 8 3 0
oo_u2hw3 (default package) Elevator notifyAllPassengers 15 3 0
oo_u2hw3 (default package) ElevatorThread ElevatorThread 5 1 3
oo_u2hw3 (default package) ElevatorThread run 43 7 0
oo_u2hw3 (default package) InputThread InputThread 3 1 1
oo_u2hw3 (default package) InputThread run 38 6 0
oo_u2hw3 (default package) MainClass main 6 1 1
oo_u2hw3 (default package) ScheduleAll addScheduler 3 1 1
oo_u2hw3 (default package) ScheduleAll addElevator 3 1 1
oo_u2hw3 (default package) ScheduleAll put 66 13 1
oo_u2hw3 (default package) ScheduleAll isSameElevator 3 1 2
oo_u2hw3 (default package) ScheduleAll canCarry 12 4 3
oo_u2hw3 (default package) ScheduleAll setEnd 3 1 0
oo_u2hw3 (default package) ScheduleAll schedule 41 9 0
oo_u2hw3 (default package) ScheduleAll getEmptyS 27 7 1
oo_u2hw3 (default package) ScheduleAll getProperS 16 5 1
oo_u2hw3 (default package) ScheduleAll wake 12 4 1
oo_u2hw3 (default package) Scheduler Scheduler 3 1 1
oo_u2hw3 (default package) Scheduler put 4 1 1
oo_u2hw3 (default package) Scheduler getMainRequest 39 9 0
oo_u2hw3 (default package) Scheduler judgeOpen 19 5 0
oo_u2hw3 (default package) Scheduler getElevator 3 1 0
oo_u2hw3 (default package) Scheduler isEnd 4 1 0
oo_u2hw3 (default package) Scheduler setEnd 4 1 0
oo_u2hw3 (default package) Scheduler isEmpty 3 1 0
oo_u2hw3 (default package) Scheduler notifyAllPassengers 15 4 0
oo_u2hw3 (default package) WrappedTimableOutput println 3 1 1

分析类图我们发现,这次好像和第二次没啥区别,确实,除了一些方法上的修改和增添,这次的结构和第二次没有任何区别,基本上这次是和第二次保持了一致的架构,对于新增需求,是在各个类内部增加约束条件来完成的。而这次的复杂度除了和第二次保持一致的那几个,put方法也变的复杂了起来,原因是put方法顺带分析了一下该Request是否可以一次到达,如果不是该怎么拆分,这导致了其较为复杂,也缺乏可拓展性。

(4)分析程序bug

只在第三次作业的互测中出现了一个bug,这个bug的出现源于我本地版本控制失误:某方法本应已经加上的synchonized关键字,被我给ctrl + z搞没了,而我也没有查觉到这一点,最终被互测发现。这个bug发生的原因是电梯线程判断电梯是否为空从而决定是否结束,判断电梯为空的方法未同步导致理应结束的电梯线程无法结束,最终tle。

 

(5)互测bug发掘策略

我在本单元采用的是自动评测的方式,生成测试数据后,利用python在多线程环境下对目标代码进行评测。评测时会按一定规则随机生成900条测试数据,通过已经构建好的ResChecker.py来检查对方的输出是否为WA,或者等待时间是否超过。测试策略的有效性体现在我三次互测均发现了同屋人的bug(也可能是我太菜进C),以及我发现bug后去阅读对方代码发现其对应的设计问题。

对于发现线程安全相关问题的策略,这里我受王鹏博同学启发,选择在多线程环境下进行测试,提高cpu的负载的同时使得多线程问题更容易触发。

本单元的测试策略和第一单元不同主要在于:本单元要考虑多线程问题的存在,有些bug在单线程环境下不一定能够复现,所以采用了多线程的方法进行评测(效果拔群。

第五次:

Archer死于电梯线程无法结束

第六次:

Rider死于电梯震荡

第七次:

Assassin死于电梯调度策略导致电梯震荡

Alterego死于电梯线程谜之消失

 

(6)心得体会

好的架构给我们带来了好的可拓展性,我终于领悟到这句话的含义了。第一次作业几乎每次都是重构,可能我原来的代码不乏可拓展性,但是至少我没有选择在其上拓展。而这次作业,我第五次作业的架构设计一直延续到了第七次,真真正正让我感受到了拓展带来的效率(第六次花费时间4小时,第七次不到3小时),虽然我的可拓展性、可读性仍然不算好,但这大概算是我第一次尝试整体性的架构规划,(虽然牺牲了一些性能分)。

对于多线程的认识不到位使我在优化的时候束手束脚,什么地方都感觉会引发多线程的问题,我最终放弃了过多的优化(也可能是劣化)。我对多线程的理解是随着代码的书写一步步增加的,但是回过头来看的话,自己最开始写的东西虽然很不让人满意,但是修改成本过大,导致我开始重复“又懂了一点多线程->我刚写的是神魔玩意؟؟”的循环,但回过头来审视,这样的螺旋上升才是学习中的常态。

OO的学习总是苦中作乐,乐中带苦,苦中有苦,乐中有乐;一单元一次的总结使人自闭(不是),希望接下来的作业中我能收获更多吧。

 

原文地址:https://www.cnblogs.com/namoe/p/12722271.html