BUAA OO Unit2`总结
1 第五次作业
1.1 同步块的设置和锁的选择
- 第五次作业中我的锁主要设在
ReqQueue
类和Output类,Output类是为了防止输出线程不安全的问题出现,此处不必赘述。ReqQueue
类中为了防止线程安全问题出现,我在方法上加了锁,同一时刻只允许一个线程访问ReqQueue
这个对象。本次作业我的同步方法均是使用的synchronized
关键字。
1.2 调度器设计
- 三次作业我均采用的是指导书给的均匀分配的模式,所以在性能分上损失惨重。并且每个电梯都有一个调度器Controller,来控制电梯的行为。
1.3 类图以及分析
InputHandler
类是一个线程类,任务是持续读入请求。当输入结束的时候,为五个请求队列设置终止信号。
- 属性:
reqQueueA
,reqQueueB
,reqQueueC
,reqQueueD
,reqQueueE
:各座纵向电梯的请求队列。
- 方法:
InputHandler
方法:初始化。
- run方法:线程主方法。获取请求,并且将请求添加到请求队列中。
Output类
- Output类是一个非线程类,任务是输出信息,以防出现输出线程不安全的问题出现。
ReqQueue
类
ReqQueue
是一个非线程类,是座电梯的待处理请求队列,任务是保存各个座的请求,并且将请求分配给电梯。是一个共享对象。
- 属性:
- requests:保存待处理请求的
ArrayList
队列。
intputEnd
:输出线程停止信号。
- 方法:
setInputEnd
方法:设置输入线程停止信号的方法,由InpurHandler
类调用。
isInputEnd
方法:返回输入线程是否停止的方法,由Controller类调用。
addRequest
方法:向请求队列添加新请求,由InputHandler
类调用。
getReqNum
方法:返回此时请求队列的请求个数的方法。
getFirstReq
方法:获取当前队列的第一条请求,适当有优化,以最早到来的请求的运行方向为基准,返回最远楼层的请求。
hasStairReq
方法:查询当前楼层是否有请求。
getStairReq
方法:获取当前楼层满足条件的请求,进入电梯。
Controller类
- Controller是一个线程类,是电梯的调度器,任务是控制电梯的运行。
- 属性:
- value:电梯的id。
reqQueue
:电梯的待处理请求队列。
- elevator:该调度器控制的电梯对象。
- output:输出类。
- 方法:
- set方法:调度器的初始化方法,控制正在等待的电梯运行到主请求的楼层。
- work方法:当前电梯正在运行,且已经到达主请求的楼层,控制运行电梯运行。
- run方法:线程主方法。利用while true循环控制电梯完成原子化操作,并且通过判断
reqQueue
的请求数量,电梯内的请求数量以及reqQueue
的输入线程终止信号来判断是否停止线程运行。
Elevator类
- Elevator是一个非线程类,是电梯类,任务是在调度器Controller的控制下,完成移动、上人、下人等操作。
- 属性:
- name:电梯所在的座号。
- value:电梯的id。
- stair:电梯当前所在的楼层。
- direction:电梯运行方向。
- output:输出类。
- 方法:
getRequests
方法:获取当前电梯内乘客请求队列的方法。
moveAll
方法:正在停止状态的电梯移动到主请求楼层的方法。
- move方法:电梯移动一层的方法。
changeDirect
方法:改变电梯运行方法的方法。
getOn
方法:电梯上人的方法。
getStairNum
方法:返回当前楼层电梯可以下人的数量。
getOff
方法:电梯下人的方法。
1.4 时序分析
InputHandle
r线程类负责获取乘客请求,将请求放入相应楼座的reqQueue
队列中。
Controller
线程不断从reqQueue
中取请求,然后控制Elevator完成接送乘客的动作。
1.5 bug分析
强测:
- bug:在电梯判断是否需要开门捎带请求的时候,少了一个判断请求的方向和电梯运行方向是否一致的判断条件,导致电梯会无故关开门,浪费运行时间。
- 改进方法:添加一个判断条件。
互测:
- bug:输出线程不安全。
- 改进方法:添加一个输出类Output。
2 第六次作业
2.1 同步块的设置和锁的选项
- 第六次作业中我的锁主要设在
ReqQueueBuild
类和Output类,还有新增的ReqQueueFloor
类,ConListBuild
类和ConListFloor
类。Output类是为了防止输出线程不安全的问题出现,此处不必赘述。ReqQueue
类和ConList
类中为了防止线程安全问题出现,我在方法上加了锁,同一时刻只允许一个线程访问这个对象。本次作业我的同步方法均是使用的synchronized
关键字。
2.2 调度器设计
2.3 类图以及分析
- 和第五次作业相比,添加了:
- 横向电梯
- 可以动态添加多部电梯
InputHandler
类是一个线程类,任务是持续读入请求。和第五次作业的区别是请求不仅仅有电梯调度请求还有添加新电梯请求,因为添加了横向电梯,并且每个座或者每个层的电梯数量不为1,所以属性有所改变。
- 属性:
conOne
,conTwo
,conThree
,conFour
,conFive
,conSix
,conSeven
,conEight
,conNine
,conTen
是各层的总队列。
conA
,conB
,conC
,conD
,conE
是各座的总队列。
- 方法:
InputHandler
方法:初始化。
- run方法:线程主方法。获取请求,判断是哪种类型的请求,执行
addPersonRequest
方法还是addElevatorRequest
方法。
addRequestPerson
方法:将电梯调度请求添加到合适的队列中。
addElevatorRequest
方法:将添加新电梯请求添加到指定座或指定层。
Output类
新增ConListBuild
类和ConListFloor
类
- 和第五次作业相比新添加的类,是非线程类。任务是保存该座或者该层所有的电梯的调度器和请求队列。以及均匀分配请求的功能。
- 属性:
- controllers:保存了所有的电梯的调度器的
ArrayList
队列。
reqQueues
:保存了所有的电梯的请求队列的ArrayList
队列。
cnt
:用于均分请求的信号。
- 方法:
addRequest
方法:用于向reqQueues
中均匀分配请求。
addConBuild
/addConFloor
方法:用于添加一步新电梯的方法。
新增ConFloor
类
- 和
ConBuild
类类似,是一个线程类,横向环形电梯的调度器类。用于控制横向电梯ElevatorFloor
完成操作。
新增ElevatorFloor
类
- 和
ElevatorBuild
类类似,是一个非线程类。任务是在调度器Controller的控制下,完成移动、上人、下人等操作。
2.4 时序分析
- 和第五次作业类似。区别在于
InputHandler
处理请求时候需要经过中间类ConlistBuild
和ConlistFloor
,中间有调度方法将乘客请求均匀分配给各个电梯。碰到增加电梯的请求时,直接在相应楼座添加电梯。
2.5 bug分析
3 第七次作业
3.1 同步块的设置和锁的选择
- 第七次作业中我的锁除了第六次作业的类之外,新增在了新的
Buildings
,Floors
和RequestNum
类上。Output类是为了防止输出线程不安全的问题出现,此处不必赘述。新增的Buildings,Floors和RequestNum
类中为了防止线程安全问题出现,我在方法上加了锁,同一时刻只允许一个线程访问这个对象。本次作业我的同步方法均是使用的synchronized
关键字。
3.2 调度器设计
3.3 类图以及分析
- 和第六次作业相比,添加了:
- 电梯的属性可以个性化定制,比如speed和capacity。
- 添加了电梯换乘的功能。
新增Buildings
类
- Buildings类是一个非线程类,共享对象,其任务是保存所有和纵向电梯有关的信息,设置这个类是为了让架构更加清晰,使纵向和横向在交互的时候不那么混乱。
- 属性:
conA
,conB
,conC
,conD
,conE
是各座的总队列。
requestNum
是电梯调度请求的总队列。
新增Floors
类
- 和
Buildings
类似,保存了所有横向电梯有关的信息。
新增RequestNum
类
RequestNum
是一个非线程类,是共享对象。保存了所有的乘客请求。设置该类的目的是为了发出一个判断电梯调度器线程是否需要终止的信号。只有当所有乘客请求都被执行完之后,调度器队列才可停止。
- 属性:
- 方法:
removeRequest
方法:当该乘客请求全部执行完毕,则从队列中移走。
addRequest
方法:添加新的乘客请求的方法。
- notifyRequests方法:可能有线程在等待队列中,每当对requestNum对象有更改时,需要唤醒等待队列中的线程。
isEmpty
方法:返回乘客请求队列是否为空。
重写PersonRequest
类为SpecPersonRequest
类
SpecPersonRequest
是重写后的PersonRequest
,任务是新增保存了和换乘有关的属性和方法。
- 新增属性:
- needFloor:是否需要横向电梯运输信号。
- needBuildingOne:是否需要第一次纵向电梯运输信号。
- needBuildingTwo:是否需要第二次纵向电梯运输信号。
- needChange:是否需要换乘信号。
mfloor
:中转楼层。如果needChange
为false,则设为0。
3.4 时序分析
3.5 bug分析
强测:
- bug:调度策略问题导致超时了。
- 改进方法:修改调度策略,每输入一个新增横向电梯请求,就更新一遍所有需要换乘请求的中转楼层。
互测:未发现bug
4 三次作业总结
4.1 总体架构设计
- 我的架构在三次作业中基本属于一致的,大体思路没有什么特别大的变动,根据题目要求进行迭代。为了使得代码更加清晰,我在第六次作业添加了横向电梯,且多部电梯的情况下,添加了
ConListBuild
和ConListFloor
来保存同一座/同一层的多部电梯。在第七次作业中为了让架构更加清晰,加入了Buildings类和Floors类。
- 时序图:
4.2 线程停止问题
- 如何停止线程是我三次作业以来最困扰我的问题。主要是在第五次作业和第七次作业中,都是如何停止电梯调度器线程。
第五次作业:
- 调度器线程停止有三个条件:
- 输入线程
InputHandler
已经停止:当输入停止时,InputHandler
在reqQueue
中设置了终止信号,调度器线程可以观察到该信号。
- 当前电梯的请求队列
reqQueue
为空。
- 当前电梯内部乘客请求队列为空。
第七次作业:
- 调度器线程停止新增了一个条件:由于其他电梯的请求队列中有未完成的请求,比如横向电梯中有一个请求需要换乘纵向电梯,此时如果纵向电梯线程已经结束,那么就会出问题。
- 设置总的乘客请求队列
RequestNum
,当调用其中的isEmpty
方法为true时,才满足最后一个线程停止条件。
4.3 调度策略问题
- 其实我一直在性能上没有下功夫,在第五次作业中,我使用的是改良版的
als
算法,在第六次和第七次多部电梯中,我也是采用的均匀分配策略,所以性能分不高。
4.4 心得体会
- 由于第一单元表达式作业前期没有做好充足的准备,导致第一次作业架构不清楚,损失惨重,第二次作业进行重构,心态炸裂,电梯单元我在开始思考自己的架构之前先充分阅读了往届学长学姐的博客,汲取前人的经验。在写代码的时候思路相对来说比较清楚。完成任务也不那么手忙脚乱了。
- 如果说有什么遗憾或者失败经验的话,就是性能上面的小遗憾。我在第六次作业中选择了均匀分配的模式,第七次作业试图改为效率更高的自由竞争,发现在最后一次迭代中改策略是一个非常不明智地选择,后来因为真的太复杂了,debug太难了,被迫改回均匀分配模式。所以,如果想追求性能的话,最好在一开始就选择最快速地策略,一定不要半途改策略。(对我而言,大佬肯定想改就改啦hhh)
- 电梯单元相对表达式单元而言,肯定是有所进步的,就是hack有点太水了。
- 希望下一个单元可以对我好一点。