面向对象第二单元(多线程调度)总结体会
前言
这三次作业的训练目的在于熟练掌握JAVA多线程同步互斥调度。其采用了比较贴合实际的电梯问题来作为载体,将多线程调度由易到难分成三个部分:
第一阶段要求了解结合多线程概念,理解电梯与请求之间的协作关系,考虑哪些资源需要被两个进程同时访问
第二阶段要求不再是单方向的信息传递,而是两个方向的信息交互,尤其注意交换信息时的同步关系
第三阶段要求采用更加科学高效的调度算法,同时满足电梯与电梯之间的互斥,电梯与请求之间的同步
1.第五次作业(FAFS电梯模拟)
1.1 程序类图展示
1.2 程序结构分析
Mainclass
为主类,通过创建Request
与 Elevator
两个线程来实现功能。
使用的是“生产者-消费者”模型,将请求队列存在queue
中,而Request
与 Elevator
一个向queue
中加入请求,一个从queue
中得到请求。
请求的加入: put(PersonRequest)
:
用来向ArrayList
请求队列中添加一个请求,该函数使用synchronized
与Elevator
进行互斥。
请求的取出: get()
:
用来从ArrayList
请求队列中取出一个请求,若queue
为空,则将Elevator
进程设为阻塞态。该函数使用synchronized
与Request
进行互斥。
请求的结束:setOver(Boolean)
:
当Request
进程死亡时,将over
位设为true
,同时唤醒可能在睡眠中的Elevator
进程。该函数使用synchronized
与Elevator
进行互斥。
1.3 优缺点分析
优点:此次作业比较完全地使用了生产者-消费者模式,而没有将调度放在电梯当中。类之间的耦合度较低,通过JAVA自带的synchronized
实现了进程之间的互斥,完成了信息的共享与交换。
缺点:没有在此基础上使用更高级的调度算法,而无法实现人员的捎带。程序实现的是实打实的FAFS模式
1.4 设计原则检测
- Single Responsibility Principle:类/方法的功能行为单一,电梯仅根据需求执行上下功能,符合该原则。
- Open Close Principle:一定程度上考虑了可拓展性,但依照开闭原则应该进一步增加框架弹性。
- Liscov Substitution Principle:无子类无继承,满足里氏替换原则。
- Interface Segregation Principle:无接口设计,满足接口分离原则。
- Dependency Inversion Principle:调度器(高层模块)不依赖于电梯(低层模块)的行为,符合该原则。
1.5 代码度量
复杂度分析
依赖度分析
本次代码的整体复杂度较低,代码规模较小,依赖度也没有超过2。整体代码量只有100+行,实现模式较为简易。
1.6 UML协作图
1.7 Bug分析
第一次程序所在房间内均为满分。既没有被找到bug,也没有找到别人bug
通过读同房间其他同学的代码,发现由于指导书要求较为明确,观察到许多同学的思路基本相同
最大的区别在于是否将控制器作为一个线程运行。
若不作为一个线程,则是标准的生产者-消费者模式;若作为一个线程,则类似于观察者模式。
2.第六次作业(单部可捎带调度(ALS)电梯)
2.1 程序类图展示
2.2 程序结构分析
从类图可以看出,第二次程序类图与第一次大体不变,主要的改动为:
- 在
Controller
中加入了getMainNeed()
方法 - 在
Elevator
中加入了mainNeed
与isOpenDoor
变量,并定义了相关的getter and setter
方法
当电梯内没有人时,访问Controller
来取得主请求;当电梯在运行时,每到一层先遍历电梯中的所有人员,观察是否需要开门出去,之后更新主请求;然后访问Controller
来了解是否需要上人,再次更新主请求。最后根据主请求上行或者下行。
2.3 优缺点分析
优点:在上次的基础上复用了较多的代码,证明整个框架的设计比较合理,且可以按照指导书的要求进行运行。
缺点:本次仅仅实现了AFS调度,而没有想到更高效的方案,使得性能分部分爆炸;在负一层与一层之间转移时使用了特判,导致之后第三次作业还需重新构思相关设计。
2.4 设计原则检测
- Single Responsibility Principle:电梯仅根据需求执行上下功能,而采用询问的方法获得主请求,符合该原则。
- Open Close Principle:一定程度上考虑了可拓展性,但在楼层转移上没有考虑复用而偷懒特判,需要反思。
- Liscov Substitution Principle:无子类无继承,满足里氏替换原则。
- Interface Segregation Principle:无接口设计,满足接口分离原则。
- Dependency Inversion Principle:控制器(高层模块)的调度依赖于电梯(低层模块)的行为,不符合该原则。
2.5 代码度量
复杂度分析
依赖度分析
复杂度分析中,两个最重要的方法复杂度明显大于其他方法,由于首先需要将queue
遍历一遍,并在其中判断,在循环中还要将不同的情况分类讨论,故需要多个if
语句,所以整体复杂度要高于其他方法。而依赖度关系情况良好,类与类之间的依赖度关系比较稳定。
2.6 UML协作图
2.7 Bug分析
此次作业强测没有被找到bug,同时也没有找到他人bug。虽然性能分近乎爆炸,但可靠性得到了保障。测试他人bug的时候,使用Random类与正则表达式随机生成了一些数据,并利用.sh
文件与管道实现了自动化评测,但各个同学的调度算法均不同,无法通过肉眼观察是否输出内容是否正确,只能通过观察是否超时来计算。但由于效率比较低下,并没有在有限的时间内找到有效的bug提交。
3 第七次作业 (多部智能(SS)调度电梯)
3.1 程序类图展示
3.2 程序结构分析
从程序类图可以发现,本次结构与上次最大的不同即为加入了一个新的Scheduler
类来完成调度任务。而输入接口接入的不再是Controller
,而是Scheduler
。而Controller
变成了每台电梯的附属控制器,用于管理控制已经分配给每台电梯管理请求的捎带。具体的调度策略如下所示:
Scheduler
通过判断请求是否可以由一部电梯独立完成,若可以则按照空闲>A>B>C的策略分配电梯- 若无法通过一部电梯到达目的地,则将请求拆分为两个,而中间的过渡楼层的判断以“运行速度快的电梯多运行,运行速度慢的电梯少运行”的原则进行选择。然后将请求的第一部分按照上一步的原则分配电梯,将请求的第二部分存在
Scheduler
的Hashmap
中,并用PersonRequest
的id
作为Key
。 - 控制器将调度器给它的请求存入自己的
queue
,并按照上次作业的算法进行电梯的捎带请求。唯一改变的是,当电梯人数已满的情况下,不进行人员进入的操作。
3.3 优缺点分析
优点:在每台电梯的内部调度部分几乎复用了所有代码,证明该框架的复用性良好;使用比较高效的静态调度算法,而由于感觉动态调度算法会破坏“高内聚,低耦合”的原则,而没有考虑使用。
缺点:在代码中有些数据可以GET INSTANCE
方式来调取使用,但却使用了调取Elevator
类中的私有类变量,提高了类与类之间的耦合度关系。在判定换乘时,按照最多换乘一次的方法进行设计的,而没有采用图论的最短路径算法,这样使得代码的复用性极差,若有下次作业,Scheduler
中的调度或许需要完全重构。
3.4 设计原则检测
- Single Responsibility Principle:调度器计算拆分需求并交给电梯控制器,电梯仅根据需求执行上下功能,而采用询问的方法获得主请求,符合该原则。
- Open Close Principle:在电梯的换乘中没有按照构造图的普适方式实现,而使用了换乘一次的方法,需要反思。
- Liscov Substitution Principle:无子类无继承,满足里氏替换原则。
- Interface Segregation Principle:无接口设计,满足接口分离原则。
- Dependency Inversion Principle:调度器,控制器(高层模块)的调度依赖于电梯(低层模块)的行为,不符合该原则。
3.5 代码度量
复杂度分析
依赖度分析
可以观察到,本次代码中复杂度较高的函数除了上次作业中的get
与getMainNeed
,还有调度器中的isSingle
(单电梯调度)和isDouble
(双电梯调度),与预料中的基本相似。而依赖度关系的上升,很大程度上是由于在数据选择上采取使用Elevator
中的数据,而没有GET INSTANCE
,需要反思。
3.6 UML协作图
3.7 Bug修复
本次强测还是没有被找到bug,也没有找到别人bug。强测的性能分较上次而言,有所提高。在寻找他人bug的时候,考虑到同房间同学的分数与我相近,仅采用了暴力测试,观察程序是否超时这一方式,而没有采用更加明智的方法。
总结
本三次作业在很大程度上使我理解了JAVA多线程编程的特点,对线程之间的同步互斥关系有了更深入的认识。在代码复用上,对比较第一次作业而言进步巨大,基本上后一次作业对前一次作业达到80%以上的复用,但在多线程调试以及测试bug中还没有做到自动化测试的效果,期待在后续的时间中,通过自学和老师的帮助,可以对多进程的其他编程思想以及测试有更高层次的理解。期待下一单元的OO练习ing!