OO第二单元总结
一、 锁与同步块
- 第一次作业
共享的对象为controller(包含等待队列的类)
(1) DownCase.run方法中完全用synchronized( elevator.controller )锁住了其中全部代码,但其中只访问了controller中的模式,并且是为了判断要不要wait释放锁,所以这个synchronize同步块无必要。
UpCase与DownCase为继承自同一个接口Mode的不同运行模式,问题相同。
(2) Elevator.work方法中完全用synchronized( controller ) 锁住了其中全部代码,run中已有对controller的锁,冗余;此外,本方法中只有进人需要访问controller中的等待队列,所以其实只要在进人和把乘客加入电梯的两句上加锁就好。
(3) ElevatorThread.run方法中对判断是否在controller上等待(电梯中无乘客)还是电梯运行的if-else分支上对controller加锁。但Elevator.work已经有锁,所以只要对controller.wait加锁就好。
(4) ScanIn.getAddWaitsMorning
ScanIn.getAddWaitsNight
ScanIn.getAddWaitsRandom
三个方法得到输入乘客后在controller的同步块中将乘客加入controller的等待队列
2. 第二次作业
共享对象为controllers(controller集成的CopyOnWriteArrayList)及其中的controller
(1) DownCase.run方法中完全用synchronized( elevator.controller )锁住了其中全部代码,但其中只访问了controller中的模式,并且是为了判断要不要wait释放锁,所以这个synchronize同步块无必要。(延续了第一次的问题)
(2) Dispatcher.newElevator方法对controllers加锁以实现加入新的controller,相关性好。
Dispatcher.addWaits方法对controllers中需要加入乘客的一个controller加锁,将同步控制到了需要操作的对象上,包括设置策略,加入等待乘客。但感觉其实可以把Controller中的end(boolean类型变量)设为AtomicBoolean,strategy(int类型变量)设为AtomicInteger,然后在controller.addWait方法中操作HashMap类型变量peoWai的时候对其加锁就能做到线程安全了,毕竟这三者逻辑不相关(只是写博客的时候的设想,本地目前测不出来较隐晦的线程安全问题,没有实践,如有不对欢迎指正)
(3) Elevator.work方法中对该电梯的controller加锁,同步块内只有“tmp = controller.addIn(nowFloor, peoIn.size())”,得到乘梯人员。在controller.addIn方法中都是对等待队列的操作,相关性较强,所以我认为这个同步是没问题的,不过考虑到将线程安全的控制集中的要求,可以把synchronized移到controller.addIn方法内。
(4) ElevatorThread.run方法中有两个分散的同步控制块,第一个是在结束时对每个controller进行notify(为了之后的换乘着想,控制了每个电梯线程同时消亡,但设计中等待的时候是还没关门的,所以要唤醒等待的电梯,让其先关门再消亡);第二个是判断是否空闲,空闲则在该电梯的controller上等待。
(5) ScanIn.println方法用synchronized修饰,保证输出线程安全。
3. 第三次作业
(1) Dispatcher类
a) newElevator方法:对CopyOnWriteArrayList类型变量controllers加锁,向其中增加一个controller。根据新加电梯的类型,对CopyOnWriteArrayList类型变量aType(/bType/cType)加锁,向其中加入新电梯序号(方便平衡投入乘客)
b) addWaits方法:对需要投入乘客的电梯的controller加锁,同步块内controller.addWait,controller.setStrategy,沿用了第二次的同步方法,简化分析同第二次。
c) setReq方法:对需要设置等待楼层的电梯的controller加锁,同步块内设置等待楼层并进行notify。
(2) DownCase类
a) run方法:完全用synchronized( elevator.controller )锁住了其中全部代码,但其中只访问了controller中的模式,并且是为了判断要不要wait释放锁,所以这个synchronize同步块无必要。(因为运行模式使用效果没问题,就一直沿用下去了,延续了前两次的问题)
(3) Elevator类
a) work方法:对该电梯的controller加锁,同步块内得到进入电梯的人员并加入电梯乘梯队列。经尝试,如果把“加入乘梯队列”移到同步块外,判断电梯线程是否需要结束时是夺得controller的锁,看controller中等待是否为空、电梯是否为空,此时有可能因取出了进入人员但还未加入造成这个线程提前结束,无法换乘。
(4) ElevatorThread类
a) run方法:同第二次,无修改
(5) ScanIn.println方法用synchronized修饰,保证输出线程安全。
二、 调度器设计
1. 第一次
无调度器,ScanIn类中读到乘客后按不同模式直接加入controller的等待队列或凑齐一部分后加入等待队列
2. 第二次
调度器为抽象类Dispatcher,实现了newElevator(新建电梯线程)、addWaits(向每个controller中加入等待乘客),而getAndAdd由其子类Morning,Night,Random实现。ScanIn类读入需求后按模式实例化三个子类中的一个,调用该子类对象的getAndAdd。
Morning中每读到6*电梯数个乘客/读到^D就将所有乘客尽量平分给每个电梯,加入该电梯的controller并进行唤醒
Night中读到^D就将所有乘客尽量平分给每个电梯,加入该电梯的controller并进行唤醒
Random中用一个标志量i标记乘客数,读入新乘客后targ = i++%n,n为电梯数,将乘客放入第targ个电梯中(平分)
每个电梯都得到了controllers(controller的大集合),但得到所有只是为了判断要不要一起结束进程,运行只访问与自己编号对应的那一个。
(这样基本平衡的分配性能还挺好的来着)
3. 第三次
调度器为抽象类Dispatcher,实现了newElevator(新建电梯线程)、addWaits(Morning或Night向controller中加入等待乘客or电梯向其他电梯的controller中加入换乘乘客)/addWait(Random向controller中加入等待乘客)、isArrive(判断乘客是否需要向某种电梯换乘)、isAoB(要换乘的乘客需要乘的电梯和最好可以等待的电梯)、setReq(向需要等待的电梯的controller设置等待楼层),而getAndAdd由其子类Morning,Night,Random实现。ScanIn类读入需求后按模式实例化三个子类中的一个,调用该子类对象的getAndAdd。
Morning中每读到18个乘客/读到^D就遍历乘客,分析需要加入哪种电梯并加入。如果该种电梯有多个,就按顺序分发。
Night中读到^D就遍历乘客,分析需要加入哪种电梯并加入。如果该种电梯有多个,就按顺序分发。
Random中和前两种模式基本相同,不过变成一个乘客一加。
每个电梯都得到了controllers(controller的大集合),但得到所有只是为了判断要不要一起结束进程,运行只访问与自己编号对应的那一个。
三、 第三次架构设计


应该算采用了LOOK和ALS结合的分布式电梯
四、 bug分析
1. 第一次
我第一次知道wait和notify原来是每个类都有的!!!在A类中notify默认是唤醒等待在A类上的线程,但在对B类进行synchronized同步的肯定是要对等待在B类上的线程唤醒呀,所以就会报出“Exception in thread "Thread-1" java.lang.IllegalMonitorStateException: current thread is not owner
at java.base/java.lang.Object.notify(Native Method)”这个错,必须要指明B.notify才是对的。
还有就是阻塞读入的输入流读入必须放在同步块外,否则读入就会阻塞该线程,一直霸占锁,直到全部读入后才会释放,导致其他需要锁的代码一直被阻塞,拖慢程序进行
2. 第二次
因为我基本上没优化,所以第一次到第二次的扩展只是把Dispatcher独立出来了,又加了几个电梯,没有bug一遍过了
3. 第三次
为了换乘必须要保证电梯线程一起结束,而结束的判断是在运行完后下一次运行前判断是否有电梯既没人等又没人乘并且end状态置位(读到^D)。我原先在Elevator类的work方法中的同步块内只有获取进入乘客队列,同步块外输出+进人。在第二次中因为每个电梯的运行时间一致,所以判断是否终结都是work之间,此时如果有乘客从controller转入电梯(进人)则必然已经完成。但第三次由于电梯的运行速度产生了较大差异,某个电梯判断结束(每个电梯都空)时恰好卡在待乘梯队列从controller中转移到elevator的中间变量但还没加入已乘梯队列之间,就导致了不正常的提前结束。
这个bug卡了我好久,甚至判断结束还用上了是否送过乘客,但由于有些简单情况根本用不上某些电梯导致程序空等,一直不结束。后来思考是不是有线程问题,分析后才发现的。
此外就是最后一个在跑的电梯线程结束前必须要让每个电梯都起来检查一下是否关门,我的电梯在闲下来的时候会保持开门的状态在原楼层等着,但有时需要结束时门没关,又由于判断需要结束的时候就直接结束了,所以就会一直开着门。
五、 互测策略
就挺离谱的,我是看代码……所以也看不出来什么,就消极防守了……
六、 心得体会
1. 官方要求
(1) 线程安全归根结底就是读写的冲突,只有在有可能造成读写冲突的地方加锁基本上就问题不大了。
(2) 新发现CopyOnWriteArrayList真的很好用,可以一边遍历一边增减,并且线程安全
(3) 策略模式和状态模式都可以把方法封装起来,调用时不需要知道现在的情况,只知道需要用就行了。而前者可以用在固定行为模式,且选择在一开始就已经给出的情况下;后者类似于有限状态机,状态随时变化且可以根据输入输出自行管理。
2. 其他碎碎念
(1) 好的架构真的很重要!!!不考虑性能,我第一单元就是按自己的理解胡写一通,完全没有设计模式,每周至少要花完整的两天才能写完,还改的非常乱,到处加代码,还要删不少;但这个单元第一次就考虑到了电梯加速、多类型,甚至连乘客会不会有多类型都想了,用了策略模式和状态模式,之后改起来就很快,第五次到第六次改了大概一个小时,第六次到第七次改了三四个小时。
(2) 还是要去学测评机(好像每次都这么说orz),手测的强度还是不够
(3) 我和大佬差距好大啊……今天研讨课听到有的大佬试了能想到的绝大多数策略,发现有的是负优化,我想了一下觉得我第七次可能也负优化了……第六次九十多分,第七次才八十多分……还是再学习设计模式吧,任重道远啊

浙公网安备 33010602011771号