面对对象 第二单元总结

面对对象 第二单元总结

第一次作业

总体架构

Main类负责线程的创建,InputThread负责读入数据并将请求放入等待队列中,ElevatorThread负责读取请求,如果这个人进入了电梯则将等待队列中该请求删除,同时将电梯中的队列增加该请求,直至其下了电梯,将其从电梯队列中删除。Scheduler作为电梯的一个属性,通过等待队列和电梯队列来判断电梯的运行方向。由于inputThread和ElevatorThread只有一个等待队列是共享数据,因此我只需要将在对等待队列进行操作时将其上同步锁即可保证线程安全。

同步块的设置和锁的选择

由于我的电梯和输入线程共用了一个等待队列,因此将PersonList类设置为线程安全类,所有的方法均使用synchronized,因此在InputThread和ElevatorThread读写该等待队列时,均需要先获得PersonList的同步锁才能进行操作,由此实现了线程安全。

调度器分析

由于只有一个InputThread向PersonList中添加请求,一个ElevatorThread从PersonList中删除请求(把该请求从请求队列中取出并放至电梯中的乘客队列),我并没有设计调度器,而是将PersonList作为一个公共托盘,电梯线程通过当前的PersonList来判断下一步的行为,即调度器安置在电梯内部。

调度策略

模仿ALS算法,产生一个主请求,当电梯队列中有人时,电梯只负责运送电梯队列中的人,将其中的一个人作为主请求,以其作为电梯的运动方向,同时如果在此过程中有人可以被捎带,则捎带,当电梯队列为空时(即所有任务均完成),电梯从等待队列中搜索最近的请求作为主请求,如此即可完成所有人的运送。

第二次作业

总体架构

与上一次作业相似,Main类负责线程的创建,InputThread负责读入数据并将请求放入等待队列中,同时负责新加电梯线程的创建,初始时创建3个ElevatorThread代表不同的电梯,每一个电梯都有一个属于其自己的电梯队列表示当前在电梯中的人。Scheduler作为电梯的一个属性,每一个电梯都有自己独立的Scheduler,通过等待队列和其电梯队列来判断该电梯的运行方向。

同步块的设置和锁的选择

在这次作业中,由于在控制人进出电梯的方法中可能出现当一个电梯获得了一个人时,还未将其从列表删除,此时已经将同步锁释放了,另一个电梯获得同步锁后也获得到了同一个人,导致两个电梯获得了同一个人,这显然是不合理的,因此我将整个控制电梯进出的方法先设置一个synchronized(PersonList)获得同步锁,待整个方法运行结束后才释放锁,避免了上述情况的发生。另外在其他有可能出现类似的方法中也使用了相同的方法来保证线程安全。

调度器分析

这次作业我使用的是自由竞争的方法,即所有电梯都和InputThread共享等待队列,在某一个电梯到达一层时,将其可以携带的人从等待队列中删除,放入自己的电梯队列,每一个电梯都有自己独立的Scheduler,其通过扫描当前的等待队列和该电梯的电梯队列来判断下一步的行为。

调度策略

电梯运行策略

由于上一次作业的效果不太满意,这次作业改用了LOOK算法,即扫描电梯运行方向上的楼层,如果还有人则继续保持该运动方向,直至上面没请求且电梯内无人还需向前,改变运行方向。如果上下楼层都没有找到请求,则将电梯停下来,直至等待队列更新。

电梯自由竞争

采取先到先得的策略,即哪个电梯先到达该楼层,哪个电梯就获得该请求。这样就会出现三个电梯都向上运行都只有一个电梯获得请求的情况,其他电梯相当于白跑了一趟,实际上这并不会造成太多的时间浪费,相反的理论上在不考虑电能消耗的时候效率较高。

首先我们看人数较少的情况,3部电梯均在1楼,有一个请求从20楼到1楼,如此的话3个电梯都会开始往上跑,但最终只有一个电梯获得了请求,在该电梯获得请求时,由于需要0.4秒的开关门时间,在这个时间内其他电梯已发现该请求消失了,于是直接调转运行方向,而获得请求的电梯之后便比其他电梯慢了0.4秒,待该电梯运行完成时,其他电梯理论上已经完成了任务,类似于木桶原理,整个系统所需的时间是由这个接到请求的电梯所决定的(运行时间最长)。

另外我们考虑很多请求的情况,3部电梯都在1楼,而有20个人从20楼到1楼,这样的话3部电梯都会前往20楼抢人,这就于我们平时做的电梯不一样了,我们平时的电梯往往都是只上来一个电梯,极大的加长了如果出现一部电梯带不下时乘客的等待时间,而这种自由竞争的做法可以保证最大数量的电梯上去接送,同时又根据人少时情况的推论,我们可以认为这个效率在大部分情况下是十分高的。

第三次作业

总体架构

沿用上次的架构,继续使用自由竞争的方式,仅修改了乘客是否应该进出电梯的判断,此外没有做太大的修改。

同步块的设置和锁的选择

本次与第二次的设置是一致的。

调度器分析

也与上次相同,仅仅增加了一个可达到楼层数组和一个判断一个乘客是否需要进出电梯的方法。

调度策略

电梯运行策略

仍然使用LOOK算法,但是将3种模式整合为一种,即本次作业实际上已经不再判断特殊的模式了,所有的模式均认为是Random模式,并以Random模式的策略运行。

自由换乘策略

做出如下假定:

  • 若一个人需要换乘,则其换乘后所经过的路程不能超过原路程;
  • 电梯总是尽可能地将乘客送往最靠近目的地的楼层,即一旦该电梯不能再继续使乘客离目的地再靠近一层(由于可达楼层的缘故),电梯将把这个人“踢出电梯”。

为了实现这些设定,我仅仅添加了一个方法needOut(),其通过判断该请求的目的地和当前楼层之间是否还存在电梯可到达的楼层,有的话则说明电梯还可以继续将该请求送往更靠近目的地的楼层,人不需要下电梯,返回非;反之如果不存在这样的楼层,返回真,代表该人需要下电梯。在有了这个方法后,我在判断一个人是否需要上电梯时添加一个新的条件!needOut,即这个人在这一层上了电梯后不需要下电梯,也即电梯可以将该请求送往更靠近目的地的楼层,否则这个人不能上该电梯。判断一个人是否需要下电梯时,添加条件needOut,如此便实现了电梯之间的自由换乘。

下面我举例说明这个流程,假设有一个人需要从1楼到达10楼,其首先被C型电梯接到,那么当C到达3楼时,发现无法继续将其送往更靠近目的地的楼层,于是下电梯,随后被B型电梯接到,那么到了9楼,发现无法继续将其送往更靠近目的地的楼层,于是下电梯,最后由A型电梯将其送至10楼。

UML分析

类图分析

画出UML类图如下所示:

画出sequence diagram图如下:

总体流程即先创建一个新的PersonList,作为等待队列,并作为参数传入其他线程的构造函数中,进而使所有线程都共享该队列,为每一个电梯创建一个新的Scheduler,传入电梯线程,之后启动各个线程。

可扩展性分析

功能设计

我实现的电梯是自由竞争式电梯,且换乘是自由换乘,无需对之前的电梯进行修改,所以功能上可以直接增加新的电梯,只需要加入一个新的可到达楼层数组即可完成新型电梯的增加,扩展性较强。

性能设计

按照上面所述的原则,从理论上来讲后续进行增加新型电梯时仍可以维持之前的性能,因为我没有一个主控制器来分配请求,也就不需要对新型电梯进行新的分配逻辑修改,换乘与请求的分配完全是自由的,只与电梯有关,因此扩展性较强。

Bug分析

第一次作业

  1. 判断电梯运行方向的函数有bug,导致电梯在俩个楼层之间来回移动。

    • bug出现位置:Scheduler.getNextFloor(int,int,int)
    • 方法度量分析:

    如下表所示,可以分析该bug出现在全项目中各种复杂都最高的类和方法中

Method CogC ev(G) iv(G) v(G)
Scheduler.getNextFloor(int,int,int) 64 19 27 29

第二次作业

  1. 电梯没有判断人数,因此可以无限加人,在中测中看似没问题,但是在强测中请求较多就出现了电梯人数多于6的情况。

    • bug出现位置:Scheduler.personInold(int,int,String,boolean)

    • 方法度量分析:

      如下表所示,可以分析该bug出现在全项目中各种复杂都最高的类和方法中

Method CogC ev(G) iv(G) v(G)
Scheduler.personInold(int,int,String,boolean) 32 1 11 13
  1. 电梯的LOOK算法出现问题,忽略了当人在本楼层的情况。
  • bug出现位置:Scheduler.nextRandom(int,int)

  • 方法度量分析:

    如下表所示,可以分析该bug出现在全项目中各种复杂都最高的类和方法中

Method CogC ev(G) iv(G) v(G)
Scheduler.nextRandom(int,int) 74 22 13 25

第三次作业

没有出现bug。

总结

这三个bug都发生在方法复杂度很高的地方,且都是在电梯的运行算法中出现问题,由于我的电梯只有一个等待队列共享队列,运行时不会出现死锁问题,也没有发生死锁问题。

Hack策略

我采取的hack策略是随机生成加手动构造,随机构造可以通过大量构造数据来测线程安全问题,手动构造则可以检测程序的性能以及是否可能出现死循环等问题。

本单元与上单元的主要区别是本单元会遇上只有某种顺序执行才会出现的bug,与上单元不同,因此需要用大量数据来寻找漏洞。

心得体会

线程安全

这次的电梯需要使用多线程的知识,我也从对多线程无法理解到逐渐会使用了线程,理解了不同同步块写法的差异,明白了如何来保证线程安全,同时也学会了如何在多线程程序中寻找bug的方法。

层次化设计

这次我的设计除了电梯的算法进行了一次修改,其他部分实际上基本没有大的改动,基本都与第一次的作业架构相同,但是也因为第一次到第二次时算法的更换导致了我第二次的作业强测只得到了4.99分,这也提醒我尽量不要对程序进行大改,否则很容易出现这种情况,今后在完成作业时一定好好构思,提前想好各个部分的构造与写法。

posted @ 2021-04-26 17:37  BUAA-YiFei  阅读(242)  评论(1编辑  收藏  举报