301
社区成员
发帖
与我相关
我的任务
分享总的来说,我的电梯调度器借用了上个学期计算机组成中的思想,我认为,调度器即根据程序当前状态给出一定的判断条件的大型黑箱元件,只需要将各个电梯线程,总请求队列以及输入线程中的信息输入Controller,Controller即会给出一些布尔值的返回值,代表对应策略是否执行。
我采用了模拟现实的较为简单的电梯策略,一方面对于电梯的接受乘客策略,我采用顺方向则接,逆方向则忽视的策略,在Controller中实现了相应的判断方法:
public static boolean ifGetPassengers() {
// 乘客所需id和电梯id相同(hw5)
// 这个乘客是否在此电梯可接送范围内(hw7)
// 电梯未满
// 电梯运行方向和乘客去往方向相同
// 电梯运行方向与乘客方向不同时这个乘客是否需要接
// 乘客所在层和电梯所在层相同
return all_conditions;
}
这样,只需要对分配给这个电梯的乘客容器进行遍历,判断每个乘客是否需要接送,若需要接送,则调用电梯中的接一个乘客方法,若不需要接送,则忽视
在hw5中,乘客是已经被分配好的,但是在hw6和hw7中,乘客没有被分配,需要自己进行调度策略,对于乘客调度策略,由于我比较担心我的编程能力不够格,我选择了随机数分配乘客,将所有乘客随机分配给某个电梯
实际上,可以认为如果在完成将总队列中的乘客均已经被调度分配到每个电梯,就可以调用hw5中的相应的电梯运行方法进行接送乘客
以hw7的UML类图为例:

我的总体架构持续了三次作业,即五个类
| 类名 | 用途 |
|---|---|
Main | 完成输入线程,电梯线程的创建 |
InputThread | 完成输入相关的信息,并将输入的信息经过处理之后加入到总队列之中 |
RequestQueue | 存储所有乘客以及电梯重置的请求队列,以及完成将总队列分配至每个电梯的接收队列 |
Elevator | 模拟一个真实的电梯,对相应乘客进行接送,完成开门关门移动等模拟操作 |
Controller | 根据已知信息指挥电梯进行某些操作 |
具体的架构细节变化发生在hw6中,我将RequestQueue类进行了整理,将电梯的接收队列移动到Controller类中,让类与类之间的分工更加明确,向着单一原则靠拢
对于未来的可扩展性,我可以想到的是加入一些新种类的满足某些特定运行条件的电梯,如之前OO课程中实现的横向移动电梯,或者在现实中经常使用的奇数层偶数层停靠的电梯,我的架构不可避免地会出现Controller过大过于复杂的情况,我计划将Controller作为一个顶层父类,下设几个不同的Controller子类,分别用来管理不同种类的电梯
Main线程主要完成的工作就是新建输入线程InputThread类和六个基础的电梯Elevator类并调用其start()方法开始线程运行,并不会涉及太多线程协作中的内容:

InputThread线程主要完成的工作是将输入的数据存入RequestQueue类之中,并在存入之后调用RequestQueue中的分配方法将乘客请求和重置请求放入Controller之中(节选):

Elevator线程则需要调用Controller中的方法,主要对已分配的请求进行处理,但是也会涉及到一些总请求队列的相关处理,例如重置之前需要将电梯内已有乘客返还至总队列之中(节选):

以上就是线程的大致协作过程
Elevator类中的方法与属性大部分为稳定内容,即在保证电梯的逻辑不发生巨大改动的情况下,只需要调用某些如同原子操作一样固定的方法,进行一定顺序的排列就能完成许多扩展操作,如moveUp(),moveDown(),reset(),openDoor(),closeDoor(),addPassengers(),removePassengers()等,同时配合Controller中的判断条件能让Elevator的run()方法变的十分简洁,可维护性与可扩展性较强。
以电梯中的reset()方法为例,在hw6中仅有普通的reset()操作,而hw7中新增了双轿厢电梯的reset()操作,我选择新增两个方法normalReset()和doubleReset(),他们一方面都调用了reset()的原子方法,进行输出,睡眠,重置属性等操作,另一方面各自又有各自不同的实现要求:
public void reset() {
// some operations
}
public void normalReset() {
reset();
// some normalReset operations
}
public void doubleReset() {
reset();
// some doubleReset operations
}
这样的操作具有让代码与方法拥有极大的可复用性的特点。
而Controller中的方法较为特殊,方法所实现的功能大部分不变,但是为了实现这一功能常常需要对代码细节进行修改。
根据此特性,我采用了分层实现功能的方法,即先实现一个最小的逻辑功能,再使用这个最小的逻辑功能与其他实现的逻辑功能一同构建一个更大的逻辑功能,直到最后实现最大的逻辑功能:
public boolean ifInRange() {
// whether the passenger is in the range of the elevator
return something;
}
public boolean ifGetPassenger() {
// some other judge
boolean inRange = ifInRange(); // use the logical segment
return someting;
}
public boolean ifRemovePassengers() {
// whether the passenger need to get out
return something;
}
public boolean ifOpen() {
for (……) {
if (ifRemovePassengers()) { // use the logical segment
return true;
}
}
for (……) {
if (ifGetPassenger()) { // use the logical segment
return true;
}
}
return false;
}
最大限度地维护代码地可重用性,尽量不修改已经实现的方法,是我认为对未知的易变内容挑战的相对可以接受的应对方式
我的架构中由于共享对象较少,严格意义上的需要进行读写保护的共享对象只有RequestQueue,需要集中保护的也是这个对象的一些方法。对于线程安全,我做的第一个努力是将RequsetQueue中的读写方法以及涉及到对RequestQueue的修改的方法进行同步块保护,但是没有成功保护线程安全,原因如下例:
public void addRequest() {
synchronized(request) {
request.add();
}
}
public void removeRequest() {
synchronized(request) {
request.remove;
}
}
public void goThroughRequest() {
for (RequestQueue requests : request) {
doSomething;
}
}
对以上三种读写方法,同步块只保证了同一时刻只能有一个线程访问这段代码,但是对读方法来说,完全存在在遍历过程中进行修改的可能,需要对读过程也用同步块进行保护,但添加同步块又可能导致死锁的情况发生,这让我在排查的过程中很是头疼,最终在代码上进行细节的修改总算是保护的较为到位了
呢其实究竟保护好不好也不清楚
为了应对轿厢互斥,我添加了六个锁对象,即Object lock = new Object();每一个双轿厢电梯中的两个电梯线程共享一把锁,在Elevator的move()方法中使用到了这把锁:
public void move() {
if (// 电梯向上) {
if (// 电梯处在交换楼层的下一层) {
synchronized (this.lock) {
while (// 有电梯处在交换楼层) {
// wait
}
// 向上移动
}
} else if (// 电梯处在交换楼层) {
synchronized (this.lock) {
// 向上移动并通知使用这把锁的另一个轿厢
}
} else {
// 向上移动
}
} else {
// 向下移动类似
}
}
这样的机制可以很好地避免有两个电梯处在同一层的情况
此次作业比较简单,共享对象也不多,没有出现bug
这次作业出现了死锁以及边遍历边修改的问题,具体解决方法在上文已经提到过
这次作业出现了等待条件出错的问题,导致电梯一直在轮询消耗cpu资源:
我的电梯停止条件应当为
(不存在重置请求) && ((输入和总队列不为空) || (另一个轿厢不为空) || (现在的分配队列没有人在这个轿厢的接纳范围之内))
而我错误地设置为
(不存在重置请求) && ((输入和总队列不为空) || (另一个轿厢不为空)) && (现在的分配队列没有人在这个轿厢的接纳范围之内)
这样会出现当最后一个乘客被分配到此电梯的另外一个轿厢,另外一个轿厢正在前往接纳的过程中,此轿厢一直不满足等待条件并轮询
层次化设计个人感觉较第一单元以及OOpre阶段的代码,已经具备面向对象编程的一些特点了,例如高内聚低耦合等,但在新学习的多线程方面还是缺乏一些熟练性,在编写代码的时候缺乏对线程安全的认识,导致之后需要花很多的时间重新阅读程序修补漏洞,十分后悔
希望课程组能够多推荐一些比较官方的多线程的相关教程或者使用样例!