概述
刚刚过去的oo第二单元主要是来训练我们java多线程设计。
借助电梯这个载体,逐步深入,线程的信息交互,控制也随之复杂。
尽管,我本单元成绩不是很好看,但是,确实学到了不少东西。
下面,我就借助分析三次作业,来谈下我的收获。
作业分析
第一次作业
(1)任务分析
本次作业需要模拟一个多线程实时电梯系统,从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出。
本次作业电梯系统具有的功能为:上下行,开关门,每运行一层的时间为固定值,开关门的时间也为固定值。
电梯系统可以采用任意的调度策略,即上行还是下行,是否在某层开关门,都可自定义,只要保证在系统限制时间内将所有的乘客送至目的地即可。
电梯系统在某一层开关门时间内可以上下乘客,开关门的边界时间都可以上下乘客。
至于调度算法-简单FAFS(傻瓜调度即可)
(2)设计策略
请求模拟器作为一个线程,负责接受在线请求并负责将请求放入请求队列中。
电梯作为一个线程,从请求队列get到所需请求并模拟电梯行为
请求队列是请求模拟器和电梯线程的共享对象,负责完成线程通信,控制。
其实,第一次作业比较简单,只需要考虑以下几个问题
问题1 电梯如何完成请求
先接到请求发出者,然后完成该请求。在第一次作业中,我们无需判断电梯运动方向,所以这个问题就比较简单了,只需要sleep盲目模拟即可
问题2 电梯线程什么时候死亡?
请求模拟器没有更多请求产生 & 请求队列等待人数为0 & 电梯中没有人
问题3 线程通信,控制
第一次作业,轮询无碍,至于同步问题,给请求队列的get() put()函数加锁即可
(3) 度量分析
代码规格
类图
复杂度分析
可以看出来本次作业的工作量不是很大
SOLID原则分析
本单元,因为我没有使用继承和接口,所以在下面的SOLID原则分析中,在下仅仅分析SRP和OCP
首先,我这次严格进行了SRP-输入类只负责输入,电梯只负责运行,调度器只负责存储请求队列。
由于是第一次作业,我暂时未考虑可扩展性
线程协作图(后面俩次作业的线程协作图也是如下,就不多次展现了)
Bug分析
第一次比较简单,侥幸没有中招
第二次作业
(1)任务分析
这次作业较第一次的改动如下
1:需要改变自己的调度方法(指导书推荐为ALS调度)
2:改变楼层限制 -3层 - -1层 和 1层 - 16层,每到一层楼需要输出Arrive at 当前楼层
3:为了便于携带,需要区分主副请求
此外需要注意的是,因为需要输出Arrive信息,所以我们需要设定电梯运行方向
为了满足评测限制,需要将调度算法改为ALS,并尽量使用notify(),wait()完成线程控制
(2)设计策略
(我没有按照指导书的方法区分主副请求,我是按最远的为主请求。)
1:当请求模拟器无法产生更多请求 & 请求队列为空 & 当前主请求完成后,结束当前电梯线程
反之,将成功获取一个主请求后,进入执行主请求的时候,每到一层,看当前楼层是否有可携带的人。
如有,看是否改变当前电梯的需到达的最高楼层和最低楼层。当电梯无人的时候 外加 第一个人接到后,结束本次任务。
2:get()函数如果发现请求队列为空的话,wait()阻塞,等待请求模拟器产生请求的时候,notifyall()通知消费者
我这样设计的话,固然性能分可能高点
但是有很大缺点,最严重的一点就是乘客体验可能极其差(强烈谴责),根本不可能用在实际生活中。
(3)度量分析
代码规模
类图
复杂度分析
emmm,可以看出来,我有些操作设计的不是很好,原因主要在于我在Elevator.run()中写了电梯的运行过程
其实,按照老师业务逻辑和行为逻辑分离的思想,我应该单独领出来该部分成为一个函数,来实现业务和逻辑分离
SOLID原则
本次作业和上一次作业基本架构相同,符合单一责任原则(SRP),
在本次作业中我没有考虑多电梯的可拓展性,所以我认为在开放封闭原则(OCP)上做的还不是很好,其他原则在本次作业中也无需考虑。
Bug分析
刚刚提到了第二次作业较第一次作业,我们需要完成电梯运行方向的设置。因为我改变了ALS调度策略,导致电梯中可能存在需要电梯向上和需要电梯向下运行的乘客
但是由于我的方向判断错误(始终以主请求来判断方向),导致电梯最后直接奔向十八层地狱,枯了,还是私下测试过少,导致的问题
第三次作业
(1)任务分析
本次作业,需要完成的任务为多部多线程智能(SS)调度电梯的模拟
较前面几次作业的区别
1:多部电梯
2:载客限制
3:可到楼层限制
4:不同电梯运行速度区别
(2)设计策略
问题1 换乘问题
设置person类 (我认为乘客应该了解自己如何换乘),一开始首先利用课程提供的接口来获取乘客的信息(id,起始楼层,终止楼层)进行检测,看单部电梯是否可以完成该请求,如若不能的话,就按照一定策略,设置该乘客的换乘楼层。
(比如可以固定一楼为换乘站,但是可以进行优化,通过比较乘客的起始楼层,和到达楼层来选择换乘楼层)
等该乘客换乘出电梯的时候,立刻根据其的信息,生成新的请求放入调度器的请求队列
问题2 电梯结束条件
这次的电梯线程终止条件发生了变化,记得加上条件 无运行电梯(为此,我在调度器中增加一个变量flag代表当前daintiness运行数量,当一个电梯当前任务完成时,cnt--并notifyall()(为了完成换乘问题,以防需要换乘的电梯已经wait()))
问题3 如何分配乘客
我依旧采取电梯之前自己来抢请求的策略。
我给每个乘坐者person设置了一个权限数组,代表可以乘坐哪部电梯。每部电梯在携带或者get主请求的时候,仅仅只能拥有当前电梯权限的电梯
所以第三次作业的框架较第二次的框架没有什么大的变化
(3)度量分析
代码规模
类图
复杂度分析
SOLID原则
本次作业输入线程负责向请求队列输入,电梯线程可能会向请求队列输入和输出,
但是我没有专门设置接口管理输入和输出,在单一责任原则(SRP)上可能做的不是很好。
至于扩展性方面,我设置Typecheck类来完成可达性检查,如果增加电梯数量的话,可以比较轻松的解决
二、互测策略
第二单元的互测难度真心比第一次的大很多,因为一个人写评测机的工作量较大,所以我自己仅仅写了一个分析最后输出结果是否正确的程序
至于如何测试代码,我是分为以下几个策略的
1:测试同时间多位顾客的请求可否完成
2:测试是否可以完成携带工作
3:测试是否可以正常线程结束工作
因为多线程的错误难以复现,有时候自己明明测试出来错误,却发现无人中刀
所以本周互测积极性不是很高
三、收获与感想
尽管,这一单元,我的成绩不是很理想,但是还是学习到了很多多线程知识。
多线程设计第一点就是要保证线程安全!线程安全!线程安全!
第二点就是要选择好的线程通信方法,轮询是不可能的,最起码也要采用wait(),notify()
这个的使用就需要注意,要不然很有可能就是死锁
不过,死锁了也不要过于着急,在wait(),和notify()周围printf一下,可以解决绝大部分问题
(printf大法好)
对了,千万不要忘记一个好的架构!要不然,一次重构一次爽,全部重构火葬场
至于优化,千千万万要保证程序的正确性,否则可能得不偿失(别问我,怎么知道滴!)
作者:陈逸恒