OO第二单元
OO第二单元总结
1.同步块与加锁分析
第二单元三次作业中均采用生产者消费者模式,InThread类作为生产request的生产者,RequestQueue类作为存储request的“托盘”,Elevator类作为生产者处理request。第三次作业采用Lock与Condition类进行同步互斥控制
1.1 synchronized
一、二两次作业使用synchronized wait notifyAll语句进行同步互斥控制InThread类将request装入RequestQueue时需要获得requestQueue的锁进入临界区,最后出临界区时使用notifyAll唤醒被阻塞的电梯消费者,request输入结束时设置requestQueue的结束标识并使用notifyAll唤醒被阻塞的电梯消费者,输入同步互斥模板如下
Request request = elevatorInput.nextRequest();
if (request == null) {
synchronized (requestQueue) {
...//do something
requestQueue.notifyAll();
}
break;
}
else if (request instanceof PersonRequest) {
synchronized (requestQueue) {
requestQueue.setOver();
requestQueue.notifyAll();
}
}
else if (request instanceof ElevatorRequest) {
...//do something
elv.start();
}
Elevator类run方法中首先进行判空操作,若请求队列为空则使用requestQueue.wait()阻塞,等待InThread的唤醒。对之后对每个访问requestQueue的临界区使用synchronized( ){ }加锁,代码模板如下
while (true) {
synchronized (requestQueue) {
while (requestQueue.isEmpty()) {
if (requestQueue.isover()) {
requestQueue.setOver();
break;
}
requestQueue.wait();
}
}
...//executing
...
synchronized (requestQueue){
...//do something
}
...
...
}
1.2 Lock Condition
第三次作业中对临界资源的访问较为频繁,考虑到synchronized是非公平锁,而且采用的调度方式是集中式调度,所有电梯访问同一临界资源(有竞争),害怕某个电梯会长时间访问不到临界资源产生饥饿,从而影响效率,因此使用了Lock类中提供的公平锁。当然如果采用对每个电梯分配一个请求队列的方式则不会有此顾虑,因为每个电梯访问的不是同一临界资源,不会产生消费者之间的竞争。
InThrea与Elevator类中的同步互斥控制逻辑与一、二次作业类似,只不过是将语句替换为Lock与Condition的语句。
if (request == null) {
lock.lock();
try {
...//do something
condition.signalAll();
}
catch (Exception e) { e.printStackTrace(); }
finally { lock.unlock(); }
break;
}
else if (request instanceof PersonRequest) {
lock.lock();
try {
...//do something
condition.signalAll();
}
catch (Exception e) { e.printStackTrace(); }
finally { lock.unlock(); }
}
else if (request instanceof ElevatorRequest) {
...//do something
elv.start();
}
while (true) {
lock.lock();
try {
while (requestQueue.isEmpty()) {
if (requestQueue.isover()) {
requestQueue.setOver();
break;
}
condition.await();
}
}
catch (Exception e) { e.printStackTrace(); }
finally { lock.unlock(); }
...//execut
...
...
}
2.调度器设计
三次作业均使用集中式调度策略,所有电梯线程共享一个请求队列,并没有为调度专门构建一个调度器类,而是将调度隐含在电梯的行为模式中

第五、六次作业的所有电梯均采取LOOK算法,即在有人的最高层与最低层之间来回移动,中途捎带所有上楼或下楼的乘客(取决于电梯是上行还是下行),因此共享队列中按所在楼层与上下楼对请求分类,电梯运行到每一层(相当于Elevator类线程每进行一次move操作)都会对请求队列进行访问,依据该层的请求释放或搭载乘客。不同的电梯线程相互独立,因而产生相互竞争。InThread类线程则将输入请求放入请求队列中,输入结束则发出结束信号,通知电梯线程在执行玩任务后结束。
第七次作业采取架构不变,任然采取集中式调度,没有特意增加调度类。调度通过实现具有不同行为模式的三种电梯类AElevator BElevator CElevator来实现。与第五、六次作业不同的是,第七次作业的Elevator类线程可以将乘客请求放回请求队列中,即在需要换乘时将乘客重新放回楼层。电梯运行到每一层(相当于Elevator类线程每进行一次move操作)都会对请求队列进行访问,访问后除了接收乘客外,电梯自行依据请求队列内的请求分布情况决定是否释放部分乘客至该楼层(即将请求放回请求队列)。
3.可扩展性分析
3.1 UML类图

3.2 UML顺序图

3.3 扩展性分析
第三次作业依然采用生产者消费者模式,InThread类负责将请求request放入“托盘”RequestQueue类,Elevator类作为消费者完成请求。另外在调度上采取集中式的调度模式,即所有电梯共享一个请求队列,以自由竞争的方式接收请求,而通过实现规定的行为模式实现自动调度。由于本次作业存在A,B,C类型电梯,故需要为三种电梯编写不同的行为模式。但三种电梯又有作为电梯的相同属性与方法,如乘客上限、移动速度、上下行状态、当前楼层等属性,开关门、移动、接收乘客、释放乘客、寻找主请求等方法。注意到不同的电梯只在某些特定行为上有所区别,如接收、释放乘客,寻找主请求,因此想到实现一个电梯类Elevator继承Thread类,该电梯类包含了电梯所有属性与方法,对于不同的电梯A,B,C类分别构造AElevator BElevator CElevator类,这三类只需继承Elevator类便能拥有电梯的基本属性与方法,重载父类的某些方法即可实现不同的行为模式。在类图中可看出三个子类重载了父类的out(),take(),drop(),find()方法,自身并没有run()方法,因为父类Elevator中的run()方法已经组织好以上四种方法的执行次序,即子类无需关心如何组织不同的方法形成完整的电梯行为模式,只需关心分解出的特定任务的具体实现。此种子类重载父类方法的模式可以在代码的扩展中遵行里氏替换原则,如有新的电梯需要实现,无需改写原有代码,只需新增一个电梯子类继承Elevator类,再重载父类方法即可。从中可以看出,为了让代码可扩展性更好,需依赖于抽象而不是具体。
4.程序bug分析
本单元在公测与互测中均未被测出bug
5.寻找bug策略
5.1 随机自动评测
与第一单元采用策略相同,搭建自动评测机,使用生成随机数据测试代码。通过数量较多的随机测试点来发现bug与潜在的线程安全问题。以实际效果来看,此种方式在两次互测中测出了与线程安全相关bug(比如输入结束后线程无法结束),但由于数据是随机分布,较难生成使代码tle的数据。因次此种方式适用于线程安全问题与电梯行为正确性的检测,但较难检测出因调度策略带来的tle问题。
5.2 分析构造边缘样例
为检测出代码的调度算法带来的性能问题,在搭建自动评测机的基础上采用分析构造边缘样例的方式。先将评测机增加性能衡量功能(具体实现:1.电梯输出最后关门时间。2.统计不同电梯的乘客吞吐量),然后分析评测机输出的性能指标(也可以直接观察代码输出),分析电梯的行为模式,最后依据电梯行为模式上的缺陷构造一些特殊的边缘样例。采用此方式,hack到了不少tle的bug,而且从结果上看,分析构造边缘样例要比随机自动评测更有效(也即hack成功率高)
6.心得体会
本单元设计多线程,尤其需要关注多线程的安全问题,当然层次化的设计也是需要考虑的。经过三次作业的实战,可以发现为保证多线的安全,应尽量使用简单的多线乘交互模式,尽量减少共享数据或将所有共享数据集中到一个类的对象中,使得lock或synchronized语句减至最少。由于本单元均只采用生产者消费者模式,将共享数据集成到一个对象中(RequestQueue),使得同步互斥的加锁对象也只有一个,很好地预防了死锁问题。在层次化设计上,要注意代码可扩展性,采用子类继承父类,重载父类方法的方式对代码进行扩展,以遵循里氏替换原则。
浙公网安备 33010602011771号