OO第二单元作业总结

一、本单元历次作业分析

第五次作业:单部电梯傻瓜调度(FAFS)

1.设计策略

  在本次作业中,我设计了两个线程类Input和Elevator,且两者共享一个请求队列request。Input类只用来处理输入的请求,每当读到新请求就将其加入队列,并唤醒等待请求的电梯线程;Elevator类则专门用来执行电梯调度的过程,每次从的队列头取出一个请求并按照请求内容调度,若此时请求队列为空,则Elevator进入等待直至Input读到新请求将其唤醒。由于本次调度采取FAFS策略,故不需要在电梯的调度算法上另做构造,只需将主要精力放在两线程间的协作以及共享对象的安全性问题上,这里当Input向request存入请求以及Elevator从request取出请求时都需要对request对象上锁,并在synchronized区域内调用request对象的wait()和notifyAll()方法。本次作业为多线程单元的第一次作业,可能目的主要在于让同学们初步认识和理解多线程并行以及相互协作的机制并能够通过使用wait和notifyAll解决CPU忙等问题,所以整个任务要求并不是很难,甚至在完成本次作业所有代码后,设计架构也只有一个主类和两个线程类,总代码行数不足120行。

2.基于度量的程序结构分析

(1)代码规模

(2)类图

(3)复杂度分析

  类复杂度:

  方法复杂度:

(4)时序图

(5)架构分析

  由于本次作业的要求比较简单,故没有太多的类和方法,从复杂度上来看,只有两个线程类的run()方法复杂度稍高,主要是因为其中包含了while(true)循环来保证线程只会在规定的条件下终止,总体来看耦合度较低,实现了方法功能的抽象划分。但自已认为也存在一些架构上的不足,因为本次调度为FAFS调度,故当电梯取到请求时调度方法便可确定,所以只在电梯类内写了一个move()方法来实现,而没有将电梯的调度过程(即上下行开门关门等行为)单独抽象成控制器。

3.Bug分析

  本次作业在强测和互测中都没有测出bug,同时自己也未在互测中找到其他同学的bug。本单元的输入需要实时投放,故测试方式不再像上一单元一样只在控制台手动输入即可,同时由于线程启动的随机性,存在出现不可复现bug的可能,因为本次调度算法很简单,故只要处理好了线程安全问题,是很难被测出bug的。

第六次作业:单部电梯可捎带调度(ALS)

1.设计策略

  这一次的作业我依然保持了上一次作业中Input和Elevator两线程模式,Input类负责读入请求并唤醒Elevator类,Elevator类处理请求完成调度。但由于调度策略不再是简单的FAFS,于是我在这一次的构造中去掉了Elevator内的move()方法,并引入了新的Controller类来调度电梯,同时乘客请求的存储方式也有所改变。在指导书中给出了一种寻找主请求确定电梯方向并捎带同方向乘客的方法,但经过自己的思考和验证,发现当电梯为空去寻找最早请求作为主请求的这一过程中是无法捎带乘客的,于是针对这一点进行了改进,取消掉了主请求概念,引入了楼层类Floor来将读入的每项请求按起始楼层分别存储,且每一层都有一个上行等待队列和下行等待队列,这样当电梯到达一层的时候只需将运行方向等待队列上的人全部进电梯即可,而不需要在扫描整个所有已读入请求队列(即第一次作业中的request队列,在本次架构中已经被按楼层拆成多个队列存在Floor类对象中)。这样的设计其实和我们日常生活所坐电梯比较像,也就是我们会在电梯外面摁上一个上行或者下行按钮来表示自己想去的方向,而电梯外面的小显示屏上会告诉你现在电梯 的运行状态是上行还是下行,如果与你的请求同向电梯就会在此楼开门接上你,否则如果电梯里无人在此层下的话就不会开门。可以发现,电梯的状态在这样的调度方法中至关重要,一共三种状态,即上行、下行、静止(等待唤醒)。当有请求唤醒等待的电梯时,电梯状态变为向该请求起始楼层运行的方向,这一过程中是可以捎带人的,而且如果电梯到达了唤醒请求的楼层且与唤醒请求反向运行的话,会优先按原方向运行,直至满足调头条件才掉头。这里所提到的调头条件是本调度算法的核心,如果没有掉头条件电梯必将向同一方向不停地运行下去,当电梯当前运行方向以上(或以下)的楼层(包括本层)已无请求且电梯内的人在此层全部下空时,电梯会在此层掉头,如果掉头后的运行方向上的楼层也无请求那么就表示已执行完所有读入请求,进入静止状态,否则就按掉头后方向再运行再捎带直至下次掉头,如此反复。

2.基于度量的程序结构分析

(1)代码规模

 

(2)类图

(3)复杂度分析

  类复杂度:

  方法复杂度:

(4)时序图

(5)架构分析

  从这一次作业的类复杂度来看,Controller类的内聚性明显高于其他类,这主要是因为本次可捎带调度算法被完全地封装在了这个类中,而由于调度算法的整体性,自己也没能找到更合适的方法来进行拆分以降低Controller类的复杂度。但从类内方法的复杂度来看的话,每个方法的复杂度并不是很高,算是较好的实现了各项功能的分工协作,比如Controller内的in()、out()、move()三个方法,将电梯进出人以及按层运行完全拆分模拟了出来,弥补了上一次作业中只有一个方法实现调度的缺陷。

3.Bug分析

  本次作业在强测和互测中都没有测出bug,同时自己也未在互测中找到其他同学的bug。与上次作业不同的是,本次作业有性能分占比,而当我设计完自己的调度算法后本以为类似日常生活实际情况,性能可能会比较高,但强测结果出来后才发现实际结果并非如此,在查看了几个性能较低的点的数据后才发现自己的算法在一些小细节的处理上还存在优化空间。一是在电梯掉头的时候,由于电梯判断掉头的前提条件是电梯里为空,这时已经开关过一次门进行了上下乘客,而此时in()方法的参数是电梯运行方向上的等待队列,显然如果电梯在本层掉头的话这个队列一定是空的,而电梯掉头后运行方向改变,在本层需要重新检查在改变后的运行方向上等待队列中有没有乘客,如果有就需要再次开关门让这一队列的乘客进入电梯,这样就会在掉头的楼层开关门两次导致了时间的浪费;二是当同时有多个请求到达来唤醒等待状态的电梯时,由于Input只能是按顺序一个个地读入,电梯会将向第一个读到的请求起始楼层的方向运行,这样就会忽略本应在同一时刻读到的电梯原来所处楼层的请求(因为这个时候Input读到的请求还未add到该层请求队列中),导致无法捎带性能降低。

第七次作业:多部电梯智能调度(SS)

1.设计策略

  这一次的作业将电梯的数量增加到了3部,而且每部电梯都有各自的运行速度和停靠楼层限制,调度算法在上一次可捎带的基础上引入了换乘问题。整体的结构还是沿用上一次作业中的两个线程类Input和Elevator且二者共享楼层类Floor来按楼层分队列存储乘客请求,并在Elevator类的构造方法中添加对外的接口参数,按照题目相应参数的要求构造三个电梯线程对象,一共进行包括Input线程的4个线程间的共享和协作。

  电梯的调度算法也是在上一次作业中运行捎带无请求则掉头的方法的基础上,由于停靠楼层的限制而做出了针对换乘方案的改进。每位乘客选择乘坐电梯的原则是这样的,如果有直达电梯就优先等直达电梯(若有多部直达电梯,则哪部电梯先到坐哪部),如果没有直达目标楼层的电梯才选择换乘(即必要时才换乘),且如果是先坐速度快的电梯然后换乘速度慢的电梯,那么就越晚换乘(即在最后一个换乘楼层下电梯)性能越好,反之慢电梯换乘快电梯就越早换乘(即在第一个换乘楼层下电梯)越好。于是在导入的jar包中PersonRequest类的基础上封装了一个新的类Person,每当Input读到一个新请求,便将该请求作为参数传入Person构造方法,通过乘客信息确定该乘客的等待电梯(wait)以及换乘情况(transFloor和transWait),由于每位乘客可能有等待多部电梯的可能,在这里设计了使用二进制数表示等待电梯的方法,二进制数0~7中每个数共3位,分别对应3部电梯,相应位为1时表示该乘客在等待对应电梯,为0时即未在等待对应电梯,每部电梯初始化时都有自己的waitId,A电梯为4(即100),B为2(即010),C为1(即001),这样在初始化乘客的wait属性时,可以使用或运算| waitId,来将相应电梯加入乘客的等待目标,电梯也可以通过与运算& waitId来判断乘客所等目标是否包含本电梯。这样一来,每部电梯的唤醒条件就不再是简单的有请求,而是发出请求的乘客的等待列表中有该电梯时才会被唤醒,并在运行方向上执行捎带策略直至掉头或再静止等待,每层捎带目标的判断也需要扫描运行方向上的等待队列从中选出等待该电梯的人,需要换乘的乘客在换乘楼层下电梯时需要再将该乘客加入该层相应方向的等待队列且重新设置所等电梯列表(即wait属性)。

  此外,上一次强测后发现的算法上的性能问题也在本次作业中进行了优化完善,首先是掉头楼层的多次开关门问题,将掉头的判断提前到电梯开关门之前,也就是说预判断电梯在此层下空且原运行方向上无请求,而并不执行这些操作,确定掉头之后运行方向改变,再开门上下人,此时扫描的上人队列就是改后的运行方向对应的等待队列了,也就是将上一次作业中在掉头楼层开关一次门只下人,再开关一次门只上人的现象合并成了一次开关门上下人,从而提高了运行性能;其次是同时到达多请求是电梯的启动方向问题,在本次作业中专门设计了一个方法updateStatus()来更新电梯状态,此方法会优先考虑在电梯当前停靠层的请求,并根据上下行距离的远近来决定优先上行还是下行,当当前停靠层无请求时才会向上下楼层扫描寻找最近的请求楼层,并选择最近目标作为新的运行方向,如此一来就解决了线程不确定性导致的同步请求不捎带问题。

  总的来说,这一次的作业很好地实现了对上一次作业设计架构和调度算法的继承和拓展,并没有像第一单元最后一次作业时出现重构现象,同时还完善了上次作业算法中性能的不足之处,算是完美收官了。

2.基于度量的程序结构分析

(1)代码规模

(2)类图

(3)复杂度分析

  类复杂度:

  方法复杂度:

(4)时序图

(5)架构分析

  通过分析上一次作业的架构,发现控制器Controller类并无自己独立于Elevator类之外的其他属性,即Controller可以完全内嵌在Elevator之中让每部电梯实现自己的调度,于是在这一次作业中取消掉了Controller类,而将捎带调度拆成私有方法写到了Elevator类自身之中,如此一来由Elevator类的3个线程对象便可以根据与Input共享的Floor类实现自我调度。总体来看,Elevator类的内聚性很高,险些达到checkStyle中300行的最大行数限制,主要还是因为上次作业中就存在的调度算法的整体性问题,同时,封装了PersonRequest的Person类在初始化每位乘客的乘电梯计划时也有较高的复杂度,因为需要传入电梯的停靠楼层作为参数(即每位乘客必须知道电梯都在哪停才能决定自己怎么做电梯),整体的功能抽象分配并不是很完美。

3.Bug分析

  本次作业在强测和互测中都没有测出bug,同时自己也未在互测中找到其他同学的bug。在优化完善了上一次作业中的性能问题后,这一次的强测结果就比较满意了,有好几个点的性能分都接近或达到了满分。但是由于本次作业是电梯自身调度的架构方式,并没有顶层控制器来寻找最优的调度方法宏观控制3个电梯,所以会存在线程运行的随机性导致的不同结果,通俗地讲,在我这样的设计中,当有多部电梯到达同一层时,电梯会共同访问该层等待队列并互相“抢人”,如果一名乘客有两部电梯可坐且这两部电梯同时到达起始楼层,那么该乘客上哪一部电梯是不确定的,完全取决于电梯线程的运行状态,乘客没有自己的选择权,所以可能会错失一些性能更好的策略,而且这样的不可复现性会在出现bug时给调试工作带来很大的麻烦。

二、SOLID设计原则分析

1.单一责任原则(The Single Responsibility Principle)

  本单元的三次作业都比较好的实现了类的单一功能,如Input只负责读取输入,Elevator通过请求进行调度,只是电梯自身的调度运行在第三次作业中被我封装在了Elevator类内部,但实际上这就是电梯自身的功能,至于是否拆出控制器似乎只是形式上的问题,本质上没有区别。

2.开放封闭原则(The Open Closed Principle)

  从上面历次作业的分析中可以发现,三次作业的整个架构都是Input和Elevator两线程类的形式,且每一次作业都可以很好地沿用上一次的构造,同时在第三次作业中也是通过一个Elevator类生成3个对象的形式来实现了3电梯而非直接构造了3个电梯类。总的来说,本单元作业很好地满足了开放封闭原则。

3.里氏替换原则(The Liskov Substitution Principle)

  第三次作业中在PersonRequest类的基础上封装了Person类,实现了对于请求对象属性的替换,使其更加符合题目调度的要求。其实这里可以让Person继承PersonRequest成为其子类,从而更好地实现里氏替换原则。

4.依赖倒置原则(The Dependency Inversion Principle)

  三次作业中未涉及到此项原则

5.接口分离原则(The Interface Segregation Principle)

  三次作业中未涉及到自定义接口的使用

三、心得体会

1.设计原则

  在课堂上了解到了SOLID原则,才意识到自己在设计架构时需要注意很多方面的内容,每一个类和方法的设计必须要功能职责明确,同时封装性和可扩展性要好,对于共性和特性结构要采用子类继承父类的代码复用形式,并在使用类的过程中考虑替换原则。

2.线程安全

  本单元首次接触到多线程编程,线程安全毫无疑问是多线程并行协作的最重要基础,经过三次作业和两次上机实验,自己算是对于共享对象方法以及线程安全类有了比较深入的理解,同时能够通过分析线程的需求,在合适的地方和部分代码处加上关键字synchronized,而非像一开始一样不管三七二十一把所有对象和方法全部上锁,并能够合理的运用wait()和notifyAll()来实现简单的线程间的通讯和协作。总的来说,收获颇多,但也有许许多多的问题,比如自己对于多线程不确定性导致的不可复现bug都还没有好的调试方法。多线程涉及很多方面的知识,本单元的学习只是简单的基础入门,未来的多线程之路还要继续努力加油。

原文地址:https://www.cnblogs.com/Miracle-dz/p/10760113.html