301
社区成员
发帖
与我相关
我的任务
分享本次第二单元作业一共有五个类(第五次、第六次、第七次作业均为五个类),分别是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的类。
以下是方法复杂度:
| method | CogC | ev(G) | iv(G) | v(G) |
| Dispatcher.Dispatcher(Requests, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.Elevator(int, Requests) | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.getDir() | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.getId() | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.getInRequests() | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.getMoveTime() | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.getPos() | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.getRequests() | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.setNewMoveTime(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.setNewVol(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Input.Input(Requests, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.Requests(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.change(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.getIsEnd() | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.getLowest() | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.getRequests() | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.getReset() | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.getSection() | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.getTransfer() | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.getUppest() | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.setIsEnd(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.setLowest(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.setReset(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.setSection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.setTransfer(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Requests.setUppest(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Elevator.resetMethod(int) | 2.0 | 1.0 | 3.0 | 3.0 |
| Main.main(String[]) | 2.0 | 1.0 | 3.0 | 3.0 |
| Dispatcher.isEnd() | 6.0 | 3.0 | 5.0 | 7.0 |
| Elevator.calDir() | 8.0 | 1.0 | 6.0 | 7.0 |
| Elevator.getFlag() | 8.0 | 5.0 | 6.0 | 8.0 |
| Elevator.kickout() | 10.0 | 1.0 | 7.0 | 7.0 |
| Input.run() | 10.0 | 3.0 | 5.0 | 6.0 |
| Elevator.out(int) | 15.0 | 1.0 | 8.0 | 8.0 |
| Elevator.calSection() | 19.0 | 8.0 | 10.0 | 11.0 |
| Elevator.exchange(int) | 23.0 | 1.0 | 13.0 | 13.0 |
| Dispatcher.run() | 25.0 | 6.0 | 9.0 | 11.0 |
| Elevator.run() | 26.0 | 3.0 | 14.0 | 15.0 |
| Dispatcher.calId(PersonRequest) | 73.0 | 1.0 | 31.0 | 43.0 |
| Total | 227.0 | 61.0 | 146.0 | 168.0 |
| Average | 5.82051282051282 | 1.564102564102564 | 3.7435897435897436 | 4.3076923076923075 |
从上面可以看出:Dispatcher类的run方法和calId方法复杂度很高,Elevator类中的run方法和exchange方法复杂度很高。其中,calId方法的行数很长,超过了checkstyle的60行限制,以后应该予以改进。
以下是类复杂度:
| class | OCavg | OCmax | WMC |
| Dispatcher | 9.25 | 23.0 | 37.0 |
| Elevator | 4.117647058823529 | 13.0 | 70.0 |
| Input | 3.0 | 5.0 | 6.0 |
| Main | 3.0 | 3.0 | 3.0 |
| Requests | 1.0 | 1.0 | 15.0 |
| Total | 131.0 | ||
| Average | 3.358974358974359 | 9.0 | 26.2 |
从上面可以看出,Dispatcher和Elevator类的复杂度较高。
以下是uml类图:

关于第三次作业实现轿厢不碰撞的方法:轿厢不动就不碰撞了。可以设置一个Boolean型共享变量,用来判断该电梯是否被某一电梯占用。如果该变量为1,则如果另一电梯要向换乘层移动的时候停止移动。
第一次作业没有出现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的难度,也提醒了我评测机的重要性。
经过了第一单元作业的洗礼,我对层次化设计有了一定的了解,但本次作业无论是单个类、方法的长度还是复杂度都较高,同时还存在着可扩展性较差的问题,希望能在之后的作业中予以改进。