oo 第二单元总结
第二单元主要是多线程的电梯调度问题
第一次作业是模拟单部电梯的运行;
第二次作业是模拟多部同型号电梯的运行;
第三次作业是模拟多部不同型号的电梯运行。
1、同步块设置和锁的选择
第一次作业
第一次作业设置了电梯线程和输入线程两个线程。其中的共享对象是乘客总的需求队列,定义了Requestqueue类。
在输入线程中读入电梯的请求加入到Requestqueue中的队列中,此时需要对共享队列加锁。
synchronized(requestqueue){
//add request
requesestqueue.notifyAll();
}
在电梯线程中从共享队列中获取请求,获得请求后将请求从共享队列中删除,在此过程中需要对共享队列加锁。
synchronized(requestqueue){
if(requestqueue.isempty() && requestqueue.isend()){
return;
}
if(requestqueue.isempty()){
try{
requesetqueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// other operations
}
第二次作业
在第二次作业中设置了输入、调度器、电梯个线程,主要存在两种共享对象的关系,分别是输入—调度器之间和调度器—电梯之间。
-
输入—调度器之间:
输入线程和调度器之间的共享对象仍为总的需求队列,输入线程将读入的需求加入到队列中,调度器从中取出并进行分发。其加锁的方式与第一次作业相同。
-
调度器—电梯之间:
电梯共设计两个队列一个是
inqueue用来存储已经在电梯内部的需求,另一个是waitqueue用来存储此电梯需要处理,但是尚未处理的请求。电梯的waitqueue与调度器共享,调度器将需求加入到某部电梯的waitqueue中,电梯在运行过程中从waitqueue中取出需求,进行处理。在调度器中和电梯中对waitqueue访问时需要加锁。
第三次作业
第三次作业总体的设计与第二次作业相同,由于涉及换乘,所以在第二次作业的基础上进行了修改。为了保证在换乘过程中的正确性,避免出现换乘前部分请求尚未处理完成,需要换乘到的电梯去处理后一部分请求的情况,电梯需要在每次执行完前部分请求再把后部分请求加回到总的需求队列中,再经过调度器调度执行。所以在第三次作业中,输入—调度器—电梯共享总的需求队列,为了保证安全性,在电梯将后部分请求返回到总的需求队列的时候需要对其加锁,其余的锁的设计与第二次相同。
2、调度器设计
-
在第一次作业中没有设计调度器,过程设计比较简单。只是电梯直接从需求队列中读取请求,在电梯中根据电梯的状态决定相应的执行顺序。
-
第二次作业调度器分析
在第二次作业中需要对多部电梯进行调度,在此次作业中设计了调度器线程。在调度器中构建一个
Arraylist作为电梯队列。调取器的主要功能是从总的需求队列中获取相应的请求,根据电梯目前的状态决定分派给哪个电梯,将此请求从需求队列中移除,加入到该部电梯对应的waitqueue中。-
具体的设计思路:
当调度器获取到一个请求时,决定加入到哪部电梯的条件为:1、该请求的方向与电梯的运行方向相同。2、为了避免超载,所以根据电梯内部运行的捎带策略获取当此请求加入电梯时电梯内的人数,即如果
inqueue内的请求到达楼层大于此请求的起始楼层和waitqueue内的请求的起始楼层大于目前所处楼层,且waitquque内的请求运行方向与当前电梯运行方向相同,并且新加入请求进入电梯时该乘客没有下电梯。这样获得了如果这个新加入的请求进入该部电梯时的电梯内的人数。如果符合上述两个条件则加入该电梯。如果没有符合上述要求的电梯,则加入到所需要处理请求总数(waitqueue.size()+inqueue.size())最少的电梯。 -
与输入线程和电梯线程的交互方法:
输入线程与调度器线程共享需求队列,当状态为结束状态时,结束该线程。当需求队列为空时,调度器进行wait等待,在输入线程中有新的请求加入到队列中,唤醒调度器线程。当输入结束时,设置成结束状态,结束运行。
-
-
第三次作业调度器分析
第三次作业的调度器设计框架与第二次相同,考虑到换乘将调度器的调度策略进行了改变。
-
具体的设计思路:
换乘的设计相对简单,只让乘客换乘一次。具体的换乘情况分为2种,1、符合长距离(自定义)运送乘客的情况且一端在C的可停靠楼层范围内,考虑A类和C类电梯的换乘。2、符合一端为奇数楼层的情况,考虑A类和B类电梯的换乘。(只有在BC类电梯相对A类电梯处于非繁忙状态(自定义)下才具有优先性)
-
与输入线程和电梯线程的交互方法:
输入线程将请求加入到总的需求队列中,调度器从总的需求队列中读取请求并根据具体情况考虑是否换乘和分配给哪部电梯,如果换乘则将请求拆分成两个请求组合加入到对应电梯的
waitqueue中。由于考虑换乘,所以此次作业的调度器线程的结束条件不仅仅是输入完成且总的需求队列为空,还需要考虑电梯中是否还有需要处理的换乘,如果没有才可以结束调度器线程。
-
3、第三次作业架构设计分析
-
架构设计:
第三次作业共设计了7个类
Mainclass类用于创建输入和调度器线程;Input线程用于读入请求加入到总的需求队列中;Dispatch线程主要负责创建电梯线程,根据电梯状态分发请求;Elevatorstate类用于存储电梯的所处楼层,电梯内人数,电梯运行方向等状态信息。考虑到换乘的需求,所以我设计的电梯的
waitqueue与总的需求队列的requestqueue的组织形式有所不同。设计了Elerequest类和Requestqueue类分别表示上述两种队列,其中waitqueue是ArrayList<Requestqueue>,Requestqueue是ArrayList<PersonRequest>。Elevator线程用以处理请求。根据上文中对于调度器调度策略的分析可知,在换乘时需要把一个请求拆分成两个并将其将整体加入到电梯的waitqueue中,每次电梯完成一个请求后根据是否还有换乘请求决定是否有需要加入回总的需求队列中的请求。 -
UML类图

-
UML协作图

-
可扩展性分析:此次作业可以完成相应的功能,并且每一个类的功能明确,各司其职,能够很好的处理相应的请求。可以通过对相应类中对应的方法进行修改来满足一定的扩展需求。
-
在功能设计与性能设计平衡方面,为了保证功能的正确性,对于性能设计并没有做太多考虑,相对设计的换乘策略以及调度策略都相对简单。
4、Bug分析
前两次作业中主要出现了 线程安全和调度的bug。主要的问题有三处,首先是在中测中出现了轮询问题和不能让线程正常停止的问题。在强测中出现了调度器线程和电梯线程中对电梯中的waitqueue的死锁问题,导致强测的场面非常惨烈。除此之外还出现了电梯调度的问题,这个问题十分愚蠢。在电梯Elevator运送乘客时,在处理Random的其中一种的情况时忘记把电梯中该下电梯的乘客送出电梯。可能是在写代码的时候头脑不够清晰,出现了这个bug,由于这个bug导致强测中又WA了几个点。
在解决了上述bug后,第三次作业中未出现bug。
5、心得体会
回顾本单元的三次作业,将从线程安全和层次化设计两方面分享我的心得体会。
-
线程安全
经历了三次作业的debug过程,深刻体会到了线程安全的重 要性。应该在设计之前思考好如何设计保证线程的安全。由于自己在写第一次作业时对于线程安全的理解不够充分,就想先完成了整体代码后,再去考虑线程的安全问题,这样给我造成了很大的困扰,并且bug不断,直到最后一次作业才算完全解决了线程安全的问题。
-
层次化设计
分析这三次作业的结构,能够做到根据功能将设计分层,不同类之间有着明显的层次,各自完成相应的功能。这种设计使得在三次作业的迭代开发较为简单,思路也较为清晰。

浙公网安备 33010602011771号