OO第二次总结
作者:互联网
OO第二次总结
一、架构设计体验
本次作业的主要目的,是实现一个多楼座(A,B,C,D,E)、跨楼座运行,支持换乘,调度等功能的多线程电梯。
对于该问题,我主张采用经典的生产者-消费者模型。从Person
类出发,构建了相应的共享资源,Table
,Dispatcher
等等。
线程及同步控制块与锁
在这一次电梯的作业架构钟,我创建了三种类型的线程:输入处理线程(InputHandler
)、乘客请求分发线程(Dispatcher
)、以及电梯线程(Elevator
)。
共享资源的同步控制
public class PersonQueue {
private static final PersonQueue WAIT_QUEUE = new PersonQueue();
private static final PersonQueue TRANS_QUEUE = new PersonQueue();
private static final HashMap<String, PersonQueue> PROCESSING_QUEUE = new HashMap<>();
private ArrayList<Person> persons;
private boolean isEnd;
public PersonQueue() {
this.persons = new ArrayList<>();
this.isEnd = false;
}
public static PersonQueue getWaitQueue() {
return WAIT_QUEUE;
}
public static PersonQueue getTransQueue() {
return TRANS_QUEUE;
}
public static HashMap<String, PersonQueue> getProcessingQueue() {
return PROCESSING_QUEUE;
}
public synchronized Person getOne() {
//TODO: this is not equal to exp version
while (persons.isEmpty() && !isEnd) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (persons.isEmpty()) {
return null;
}
Person ret = persons.get(0);
persons.remove(0);
notifyAll();
return ret;
}
public synchronized void put(Person p) {
this.persons.add(p);
notifyAll();
}
public synchronized ArrayList<Person> getPersons() {
return persons;
}
public synchronized void putAll(ArrayList<Person> ps) {
this.persons.addAll(ps);
notifyAll();
}
public synchronized void setEnd(boolean isEnd) {
this.isEnd = isEnd;
notifyAll();
}
public synchronized boolean isEnd() {
return isEnd;
}
public synchronized boolean isEmpty() {
return persons.isEmpty();
}
}
本次对共享资源的控制实现机制为:在有投喂请求且获得共享资源锁的情况下,直接投递相应的共享资源。并唤醒等待中的所有线程去获得新请求。而对于消费者线程,既电梯,在没有新的请求到来且输入尚未结束时,会在自己所拥有的共享资源上等待。并继续执行之前的行为。
调度器分析
这一次作业的要求中,我们需要完成同一座楼层多部电梯的协同,以及不同楼座横向电梯之间的换成等行为。为此,我的调度器分为以下几个行为逻辑。
stay
:在当前乘客列表为空,且无新请求的情况下。- 非
stay
:每到一层楼调用controller
的方法,判断下一次前进放向,上下乘客。- 首先根据当前到达楼层,获得乘客列表中所有需要离开电梯的乘客。
- 根据新乘客列表,更新电梯当前状态。
- 若还有乘客,则依据乘客的需求前进。
- 若无乘客,则判断上一次状态,若上一次的方向上还有请求则同方向,否则,转向。
- 根据更新后电梯的状态,筛选上电梯乘客。
在这个过程中,对于多部电梯的情况,我采用了多部同类型电梯共享一个资源对象,并通过在电梯控制时,synchronize
标记共享资源,实现互斥访问。因此对于每一个电梯的调度行为,都是原子性的,不会由此发生线程安全问题。
同时,针对多部电梯,采用自由竞争方案。这样做的好处,我认为是在局部上为最优的贪心策略。既我不考虑各种刁钻的边界情况,让电梯的性能决定载客行为。
而之后为了满足乘客出发楼层、楼座和目的楼层、楼座都不相同的换乘情况。我针对三种不同的乘客路线进行对乘客类的相应封装。
对乘客类:
public int getFromFloor(String building) {
if (fromBuilding.equals(toBuilding)) {
return fromFloor;
} else if (building.equals(fromBuilding)) {
return fromFloor;
} else {
return transFloor;
}
}
public int getAimFloor(String building) {
if (fromBuilding.equals(toBuilding)) {
return toFloor;
} else if (building.equals(fromBuilding)) {
return transFloor;
} else {
return toFloor;
}
}
对外部电梯调度器,通过指定当前乘客所属楼座或楼层,得到相应不同楼座下乘客的出发点与目的地,实现对前两次作业中电梯的复用。
而对于乘客中转的操作,我的实现策略是,新增一个TransDispatcher
类。对于任何一个出电梯乘客而言,会被电梯放入一个transQueue
中,并由TransDispatcher
判断其是否需要放回电梯的共享资源中完成之后的路程。
扩展性分析
UML图
协作图
架构分析
对于可拓展性问题,首先针对功能上的拓展;例如电梯实现更多变的功能,比如楼层可达性等等,由于本实现架构为电梯控制器和电梯类分离,因此可以仅更改电梯控制器的逻辑,以及相应输入解析逻辑便能完成;同时针对其余,如新乘客需求等等任务,都可以针对本架构实现原有类的包装,使其暴露的接口同这次作业相同完成相应的功能。
为了实现本次新增的换乘功能,在原有策略上,新增了换乘调度器,由其负责乘客出电梯后的分配工作,实现了与原有架构的高内聚,低耦合。每个模块仅完成其自己的功能。
二、问题、bug、共性问题分析探讨
针对不同次作业出现的bug、及共性问题在此处探讨。
作业五
作业五中,我及周围同学所面对的最大的问题是输出时间戳不递增,对于这个问题,大部分同学都是忽略了输出的线程安全性所导致的。对于java
内置的大部分包,几乎都不是线程安全的类,除了少数的原子性类,多个线程对其进行操作都是不安全的。因此,在调用这些共享资源时,我们需要获得相应的锁,使得在每一次操作时有且仅有一个线程能对其进行读写。从而实现线程安全。
除此之外,我还犯了一个严重的错误,对于电梯容量问题的处理不当,我采用了List
作为存储乘客的容器,并在赋值时错误地使用了向下类型转换,导致失败报错。且该问题在中测,以及我自己的测试中完全没能体现。这是由于对于基本测试的不全面性导致的。
作业六
针对作业六,我在互测中暴露出来的bug主要是CPU轮询。对于轮询这个问题,由于在询问共享资源状态的函数比如isEnd
、isEmpty
中,多余地对拥有共享资源监视器的线程notifyAll
。这会导致,等待新乘客的线程被频繁唤醒,占用大量CPU时间。所以在之后的作业中我明晰了以下两条。
- 在对目标进行写操作、或更新状态时,需要
notifyAll()
,来保证等待的线程能获得新资源。 - 在对目标进行读操作时,不需要
notifyAll()
,因为读操作并不会给等待线程带来新资源,因此避免notifyAll()
频繁唤醒。
作业七
针对作业七,我了解到的部分同学存在的共性bug与轮询有关,既同我之前所讲。同时有一部分同学存在,换成过程中,电梯因为接错人的问题,来回震荡。也有可能去往不该前往的楼层。而我自己在这一部分暴露出来的问题是,线程安全结束的问题。由于我的逻辑是,在输入结束以后才会判断是否需要停止所有线程,且该判断是基于乘客请求的数量。这会导致在没有新乘客输入,但有新电梯输入时,由于结束判断在等待新乘客无法被唤醒,导致无法判断是否应当结束的问题。
对于该问题,我的后续解决方案是,在结束时送入一个特别的乘客请求,标志输入结束来避免这个问题。
发现他人bug的策略
我所采用的策略为:构造边界数据+随机数据测试:
构造边界数据:该方法分为两类,一类是行为边界数据,一类是性能边界数据。
- 行为边界数据是指:考虑电梯的运行逻辑等待,例如第七次作业中出现换乘,可以需要不同换乘模式的乘客进行测试。比如,需要先到达换乘楼层再换乘,或者已经到达换乘楼层,直接换乘等等。或者是考虑电梯结束策略,在不在送入新乘客后,新增电梯请求。
- 性能边界数据是指:考虑电梯极限状态下的运载逻辑是否正确,比如在同一时间塞入大量乘客,测试代码对于极端情况的鲁棒性。
构造边界数据的方法非常有效,因为该方法是针对本次作业中极易出现bug的地方,进行针对性测试。这也是我们许多同学存在的共性问题。
随机数据测试:则是由Python
脚本生成随机数据,由随机数据指导hack,本次作业中,该方法效率较低,因为平常逻辑的电梯,很难出现重大问题。对于未针对的数据,往往只能测试到平常的一些点。很难有效的直接命中。
三、度量分析
类名 | OCavg | OCmax | WMC |
---|---|---|---|
base.Person | 1.65 | 7.0 | 33.0 |
base.PersonQueue | 1.18 | 3.0 | 13.0 |
controller.BEleController | 5.33 | 13.0 | 32.0 |
controller.FEleController | 3.88 | 11.0 | 31.0 |
dispatcher.InputHandler | 4.0 | 9.0 | 24.0 |
dispatcher.PersonDispatcher | 2.0 | 4.0 | 6.0 |
dispatcher.TransDispatcher | 2.5 | 7.0 | 10.0 |
elevator.BuildingElevator | 1.42 | 6.0 | 30.0 |
elevator.FloorElevator | 1.47 | 5.0 | 31.0 |
MainClass | 2.0 | 2.0 | 2.0 |
util.CircleUtil | 3.0 | 5.0 | 9.0 |
util.StringUtil | 1.0 | 1.0 | 1.0 |
Total | 223.0 | ||
Average | 2.10 | 5.69 | 17.15 |
由类复杂度可以看出,由于本次作业我未针对不同电梯类,以及相应的控制器进行抽象,层次化处理,相应的部分复杂度较高。特别是Controller
类涉及不同电梯的不同行为逻辑和模式,出现了较大的重叠部分,导致其调用复杂度偏高。
四、本单元学习的心得体会
本单元我们学习了多线程,同步控制等知识。这在OO课程中,我认为是提供给我了一个入门的接口。并让我了解到了更多,高阶的同步控制方法如ReEntryLock
。
同时也了解到了,多线程中经典的一些模型,例如生产者-消费者,单例模式等等。这对我之后对于多线程更深一步,其他方面的学习也会有所帮助。
标签:OO,总结,return,乘客,共享资源,电梯,线程,第二次,public 来源: https://www.cnblogs.com/carkham/p/16215083.html