2019年北航OO第二次博客总结

一、多线程电梯系列作业设计策略

1. 第一次作业——"FAFS傻瓜电梯"

第一次作业是先来先服务的"傻瓜电梯",我当时觉得这个设计未免太简单了,于是就在傻瓜电梯的基础上加上贪心算法,每次都执行电梯内外距离最近的请求(但是没有行进中的捎带)。由于第一次没有限制CPU时间,而且我的wait--notify用得不太熟,因此就采取了暴力轮询的方式。我将我将调度器线程写在了main函数中(这是个很不好的设计,第三次作业中将其改进)作为一个线程,电梯作为一个线程,输入作为一个线程一共三个线程。另外我设计了一个personlist的类以及一个person类,这个类里面有一个属性是flag,flag表示这个人的状态,我规定了三种状态(0:未上电梯1:在电梯内2:在电梯外),这样我就不需要对personlist做remove操作,只需要每次都遍历personlist看它里面所有person对象的状态即可,人上下电梯也就变成了对flag的更改。其中输入线程阻塞式输入,每次输入之后转换为一个person对象加入到personlist中,然后电梯线程不断的轮询:判断是否开门—>开关门—>寻找下一个最近的楼层—>去最近的楼层。在开关门阶段电梯只负责开关门,人上下电梯由调度器负责,电梯开关门是将开关门标志位置为true,然后电梯sleep,这时调度器线程控制人的上下,当输入线程结束后,调度器线程就会不断判断是否personlist中的所有人的flag均为2,如果是而且电梯线程的门是否已关标志位置true,则调度器线程结束,电梯线程我设置为守护线程,在调度器线程和输入线程都结束后它也会平稳结束。

2. 第二次作业——"ALS可捎带电梯"

第二次作业是同向请求可捎带的“ALS电梯”。我的线程设置和上次相同,有两点改变。第一,为了限制CPU时间避免暴力轮询,我使用了wait--notify让线程在不需运行的时候wait,这是就需要多线程的协同与通信。在输入线程每输入进来一次请求时,都会notifyAll一次,这时会唤醒电梯线程(我是采用标志位置true+notifyAll来实现唤醒特定线程的),在输入线程结束后会唤醒调度器线程看是否需要结束程序。在电梯线程中,每次开关门时要唤醒调度器线程让它负责人员上下,每次关门要唤醒调度器看程序是否可以结束。第二,在电梯运行过程中每到一层都会判断是否有同向可稍带请求,如果有的话就执行捎带,如果没有的话继续运行即可。

3. 第三次作业——"多电梯"

多电梯是一个很复杂的问题,我的思想就是化繁为简,ABC电梯被我设置成了三部ALS电梯,三个线程。每个电梯有一个调度器,也是三个线程。这样一共六个线程,电梯的三个线程为守护线程。只要一个电梯能够执行的请求我就会让一个电梯来执行,遇到了一个电梯执行不了的请求,我会设置一个固定的楼层让它换乘,这些工作在电梯读入请求前就做好了,这样每个电梯只管自己可以接的请求,就相当于三个ALS的结合,感觉也是我对"请求调度与请求实现分离"的理解。

二、基于度量对程序结构的分析

1. 第一次作业

1.1 复杂度分析

本文采用MetricsReloaded插件进行复杂度分析,其中OCavg代表类的方法的平均循环复杂度,WMC代表类的方法的总循环复杂度。

1.2 UML 类图分析

本文采用IDEA内置diagram生成类图。

优缺点分析:我新建了一个person类去存请求(就相当于人),personlist是请求的一个队列,有三个线程。优点是实现简单,把电梯选请求都放到电梯线程中易于编码;缺点是该分离的功能没有分离实现,调度请求本应该是调度器的功能却交给了电梯去实现。

1.3 时序图分析

1.4 SOLID法则分析

由于没有继承和接口,因此对于LSP,ISP,DIP法则暂不考虑。

对于SRP,每个类都有一个明确的职责。这个完成的不太好,我的电梯类既进行了运行电梯,又进行了选择请求(相当于调度),应该让每个类的功能明确且单一。

对于OCP,感觉实现的还可以,之后的两次作业都可以套用这个架构,电梯的运行机制基本不变,需要改变的只是调度的机制和扩充为三部电梯,符合“无需修改已有实现,通过扩充来添加新功能”。

2. 第二次作业

2.1 复杂度分析

2.2 UML 类图分析

优缺点分析:优点是实现简单,把电梯选请求都放到电梯线程中易于编码,还有一点就是我把所有的输出写成了一个Output类,体现了面向对象封装的思想;缺点是该分离的功能没有分离实现,调度请求本应该是调度器的功能却交给了电梯去实现。

2.3 时序图分析

2.4 SOLID法则分析

由于没有继承和接口,因此对于LSP,ISP,DIP法则暂不考虑。

对于SRP,每个类都有一个明确的职责。这个完成的不太好,我的电梯类既进行了运行电梯,又进行了选择请求(相当于调度),应该让每个类的功能明确且单一。

对于OCP,感觉实现的还可以,第三次作业就相当于三部这样的电梯,电梯的运行机制基本不变,ALS调度也不须改变,符合“无需修改已有实现,通过扩充来添加新功能”。

3. 第三次作业

3.1 复杂度分析

3.2 UML 类图分析

优缺点分析:优点是实现简单,把电梯选请求都放到电梯线程中易于编码;把所有的输出写成了一个Output类,体现了面向对象封装的思想;请求调度与请求执行分离,每个调度器和电梯相当于一个第二次作业,三个这样的系统就组成了这次作业,各类之间耦合度小,正确度高不宜出错。缺点是该分离的功能没有分离实现,调度请求本应该是调度器的功能却交给了电梯去实现;三个电梯和三个调度器本可以致谢两个类而我却在想保证正确性和性能想法的驱动下写了6个类。

3.3 时序图分析

3.4 SOLID法则分析

由于没有继承和接口,因此对于LSP,ISP,DIP法则暂不考虑。

对于SRP,每个类都有一个明确的职责。这个完成的还可以,虽然电梯类既进行了运行电梯,又进行了选择请求(相当于调度),但是每个电梯和对应的调度器的这样的小系统是功能非常单一的,系统之间耦合度低。

对于OCP,完成的不好,调度方法基本没有什么扩展能力,电梯越多复杂度越高,可扩展性低。

三、自动化测试与ReentrantLock

1. 自动化测试

由于我这三次作业的hack次数和被hack次数均为0,因此就来谈谈测试吧。多线程程序由于它程序执行结果的不确定性以及程序bug的难以复现性,再使用手动输入测试的方法显然不合时宜。这时我们就需要利用一些自动化的测试方式来测试我们的程序。主要的步骤是

1)使用python开启一个子进程,启动待测程序

2)通过sleep完成定时输入,和第一步骤中开启的子进程通信

3)检测输出是否符合要求

2. Lock与ReentrantLock

这三次作业处于对正确性的追求,我一直在用synchronize加锁,而在实现唤醒特定线程时我使用的方法是,设置特定线程标志为true然后notifyAll.然而,实际上完全可以用功能更为强大的Lock和实现了Lock接口的可重入锁ReentrantLock.Lock比synchronize的优势主要体现在三个方面:

1)可以让等待的锁响应中断

2)可以知道是否成功获得锁

3)可以提高多个线程进行读操作的效率

ReentrantLock+Condition可以更加优美的实现唤醒特定的线程。

四、心得与体会

1. 线程安全

为了提高程序的执行效率,我们常常采取多个线程并发执行的方式。然而多线程中线程执行时序的不确定性会导致线程不安全的事情存在。常见的read-then-write,check-then-modify模式的代码都会造成线程的不安全。那么在多线程编程中我们如何可以尽可能地保证线程安全呢?有以下几点值得注意的地方:

1)使用不可变对象,用final强制限制对属性成员的修改

2)保证操作的原子性:使用Atomic***类型变量

3)保证更改的及时可见:violate关键字的使用

4)读写访问的互斥控制:对需要同步的代码块加锁

总结起来,要想保证你的设计是线程安全的,这里有三个要素:

1)严格控制对象的发布与共享

2)将共享对象设计为线程安全类

3)线程类要保持简介

2. 设计原则

这三次作业我用到了面向对象的一些设计模式,还有我自己的一些设计原则

1)如果全局只存在一个对象(比如调度器),则采用单例模式构造调度器对象

2)如果一个对象的状态发生改变,需要另外一些依赖它的对象收到通知并自动更新,则可以使用观察者模式

3)Worker Thread模式也是一种很好的设计模式,应用于我们本次电梯作业可将请求调度与请求实现分离

4)实现简单,架构清晰,正确度要比性能分更重要

3. 一些反思与展望

时光飞逝,转眼间已经过去6次OO作业了,我们本学期的OO征程也已经过半,这两个月以来,虽然非常的辛苦,但是的的确确收获了很多,虽然自己还有很多不足,但是对本学期OO的前半部分还是比较满意的,希望自己不断努力,继续加油!

猜你喜欢

转载自www.cnblogs.com/ljyhero/p/buaaoo-ljy2.html