BUAA OO 第二单元总结

张泽阳-22373554 学生 2024-04-19 17:08:32

一、同步块与锁

本次第二单元作业一共有五个类(第五次、第六次、第七次作业均为五个类),分别是Main类、Request类、Input类、Dispatcher类和Elevator类。

1.Main类和Request类中没有同步块

2.Input类用于输入信息,一共有三个同步块

1)第一个同步块,用于将输入的请求传入allRequests中,是一个写操作,所以以allRequests为锁

2)第二个同步块,用于将普通重置的信息传给对应的电梯,是一个写操作,所以以该电梯对应的Requests为锁

3)第三个同步块,用于将双轿厢电梯重置的信息传给对应的电梯,是一个写操作,所以以该电梯对应的Requests为锁

3.Dispatcher类用于调度,一共有八个同步块

1)run方法中有三个同步块,第一个同步块以allRequests为锁,用于将已经分配给具体电梯的请求移出allRequests;第二个同步块以各个电梯的Requests为锁,用于将对应的请求分配给相应电梯的序列;第三个同步块也以各个电梯的Requests为锁,当Dispatcher线程结束的时候将结束信号传递给各个电梯

2)isEnd方法用于判断该线程是否应该结束,有一个同步块,以allRequests为锁,用来读取序列判断所有请求是否分配完毕

3)calId方法计算请求应该分配的电梯编号,有四个同步块,均以每个电梯的Requests为锁,都是用来在不同的情况下读取电梯的各种信息,用以计算应该将请求分配个哪个电梯

4.Elevator类实现具体的电梯,一共有十六个同步块

1)run方法中有四个同步块,除第三个同步块外,均以Requests为锁。第一个同步块读取reset类型的相关信息,第二个同步块判断线程是否应该结束,第三个同步块为空,当dir==0时(即Requests和InRequests均为空)唤醒以allRequests为锁的线程(为了避免死锁),第四个同步块用于使线程陷入wait

2)resetMethod方法用于实现两类reset,有五个同步块。前两个同步块用于实现第一类重置,能够将reset重置为0和唤醒以allRequests为锁的线程(为了避免死锁),后三个同步块用于实现第二类重置,能够计算出此时一共调用哪个轿厢,将reset重置为0和唤醒以allRequests为锁的线程(为了避免死锁)。

3)calSection方法用于计算使用哪个轿厢,有两个同步块。两个同步块的锁分别是Requests和allRequests。

4)kickout方法用于在电梯重置之时将所有电梯中的人赶出轿厢,有两个同步块。两个同步块的锁分别是Requests和allRequests,分别用来移出已分配的请求和接受取消的请求。

5)exchange方法用于开关门和上下乘客,有两个同步块。两个同步块的锁分别是Requests和allRequests。

6)calDir方法用于计算电梯运行的方向,有一个同步块。该同步块的锁是Requests,因为在该方法中需要读取Requests和InRequests中的相关数据以计算电梯应该向哪个方向移动。

二、调度设计

1.第一次作业:

第一次作业的调度器非常简单,因为第一次作业制定了对应的电梯,所以只需要将分配电梯的序号直接输出即可。电梯的运行采用ALS策略。

2.第二次作业:

第二次作业由于没有指定电梯,所以大大扩展了调度方法。首先遍历六部电梯,在没有reset的电梯中选择顺路且所需时间最短的,如果没有,则仍在没有reset的电梯中选择Requests和InRequests中的请求数量最少的电梯,最后,如果仍然没有合适的电梯,则sleep(1200)后继续计算。(注:以sleep方法来避免线程冲突会导致死锁bug,会在第三次作业中修改)电梯的运行采用ALS策略。

3.第三次作业:

第三次作业与第二次作业的调度类似。只不过在前两种分配仍然没有结果的电梯的时候,优先把请求分配给能够直达的电梯。如果没有能够直达的电梯,如六部电梯全部被reset,则将该请求随机地分配给电梯A/B所在的区域。电梯的运行采用ALS策略。

三、架构设计

本次作业使用的都是生产者——消费者模式。在三次作业中,并没有新增类,只是在各个类中增加了许多方法和属性。在Dispatcher类中增加了isEnd方法,因为原有的isEnd一个属性已经没有办法满足描述线程是否结束:allRequests为空 && 所有电梯的Requests与InRequests为空 && 所有电梯不处于reset状态。在第二次和第三次作业中都大大扩展了calId方法。在Input类中增加了对于两个重置请求的解析。在Elevator类中,新增了处理两个重置请求的方法,第三次作业新增了用于判断A/B轿厢的calSection方法,新增了kickout方法用于将所有的人赶出电梯。

如果未来还有扩展,既可以在Dispatcher类和Elevator类中增加新的方法,也可以新建新的继承自Elevator的类。

以下是方法复杂度:

methodCogCev(G)iv(G)v(G)
Dispatcher.Dispatcher(Requests, ArrayList)0.01.01.01.0
Elevator.Elevator(int, Requests)0.01.01.01.0
Elevator.getDir()0.01.01.01.0
Elevator.getId()0.01.01.01.0
Elevator.getInRequests()0.01.01.01.0
Elevator.getMoveTime()0.01.01.01.0
Elevator.getPos()0.01.01.01.0
Elevator.getRequests()0.01.01.01.0
Elevator.setNewMoveTime(int)0.01.01.01.0
Elevator.setNewVol(int)0.01.01.01.0
Input.Input(Requests, ArrayList)0.01.01.01.0
Requests.Requests(ArrayList)0.01.01.01.0
Requests.change(ArrayList)0.01.01.01.0
Requests.getIsEnd()0.01.01.01.0
Requests.getLowest()0.01.01.01.0
Requests.getRequests()0.01.01.01.0
Requests.getReset()0.01.01.01.0
Requests.getSection()0.01.01.01.0
Requests.getTransfer()0.01.01.01.0
Requests.getUppest()0.01.01.01.0
Requests.setIsEnd(int)0.01.01.01.0
Requests.setLowest(int)0.01.01.01.0
Requests.setReset(int)0.01.01.01.0
Requests.setSection(int)0.01.01.01.0
Requests.setTransfer(int)0.01.01.01.0
Requests.setUppest(int)0.01.01.01.0
Elevator.resetMethod(int)2.01.03.03.0
Main.main(String[])2.01.03.03.0
Dispatcher.isEnd()6.03.05.07.0
Elevator.calDir()8.01.06.07.0
Elevator.getFlag()8.05.06.08.0
Elevator.kickout()10.01.07.07.0
Input.run()10.03.05.06.0
Elevator.out(int)15.01.08.08.0
Elevator.calSection()19.08.010.011.0
Elevator.exchange(int)23.01.013.013.0
Dispatcher.run()25.06.09.011.0
Elevator.run()26.03.014.015.0
Dispatcher.calId(PersonRequest)73.01.031.043.0
Total227.061.0146.0168.0
Average5.820512820512821.5641025641025643.74358974358974364.3076923076923075

从上面可以看出:Dispatcher类的run方法和calId方法复杂度很高,Elevator类中的run方法和exchange方法复杂度很高。其中,calId方法的行数很长,超过了checkstyle的60行限制,以后应该予以改进。

以下是类复杂度:

classOCavgOCmaxWMC
Dispatcher9.2523.037.0
Elevator4.11764705882352913.070.0
Input3.05.06.0
Main3.03.03.0
Requests1.01.015.0
Total  131.0
Average3.3589743589743599.026.2

从上面可以看出,Dispatcher和Elevator类的复杂度较高。

以下是uml类图:

 

关于第三次作业实现轿厢不碰撞的方法:轿厢不动就不碰撞了。可以设置一个Boolean型共享变量,用来判断该电梯是否被某一电梯占用。如果该变量为1,则如果另一电梯要向换乘层移动的时候停止移动。

四、bug与debug

第一次作业没有出现bug。

第二次作业出现了大量bug,所有的bug都和线程安全有关。一类是死锁,是由于两个线程取得锁的顺序相反引起的,通过增加锁或改变锁的顺序来实现。另一类是程序不能正常结束,这是因为isEnd在Dispatcher类中进行判断,而Dispatcher类平时处于wait状态,只有在notify的时候才会被唤醒,而电梯线程执行完所有任务时有时并不会唤醒调度线程,从而导致所有线程的isEnd均为0而不能正确结束,解决办法是增加了许多只有唤醒作用的同步块。

第三次作业出现了两个bug,一是在六个reset同时进行时有概率触发的死循环问题,当六个电梯全部reset的时候,会sleep1200ms,重新检查六个电梯是否重置完成,但是sleep过程中Dispatcher仍然拥有allRequests的锁,导致电梯线程不能获得对应的锁而不能重置,解决办法是将sleep改为wait,并且由if变为while循环,二是将人错误地分配到A/B相反的电梯之中,通过改变调度器分配的逻辑而解决了这个bug。

五、心得体会

第二单元的线程是从未接触过的,从一开始完全不知道synchronized、notify、wait、sleep的意义到逐渐理解线程的本质,并且尝试用读写的lock/unlock来实现线程安全,在这个过程中,我逐渐领悟了线程安全的真实含义。同时,多线程也带来了许多前所未有的bug,比如死锁,这些bug与以往的bug并不相同,而且难以复现,增加了debug的难度,也提醒了我评测机的重要性。

经过了第一单元作业的洗礼,我对层次化设计有了一定的了解,但本次作业无论是单个类、方法的长度还是复杂度都较高,同时还存在着可扩展性较差的问题,希望能在之后的作业中予以改进。

...全文
56 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧