OO-第二单元总结
设计分析
OO课程的第二单元作业以多线程设计为核心,主要内容是设计一个支持及时请求响应的多部电梯系统。
同步块与锁的选择
在多线程设计过程中,最为重要的就是线程协作过程中安全性的保证。多个线程之间一定存在着信息的交互,交互就意味着存在着某些共享的数据对象。然而由于线程的调度本身是由JVM进行管理的,无法通过编程进行控制,因此为了在调度不透明的情况下保证代码执行的正确性,就需要维护共享数据对象的线程安全性,因此就引入了同步块或者锁。
对于Java多线程编程而言,保证线程安全可以使用Java自带的synchronized
关键字对需要同步的代码块进行修饰,也可以使用lock
等更为精细的手段实现。在完成本单元作业的过程中,因为对于多线程的理解不是特别熟练,同时基础的synchronized
实现同步基本上可以完成三次作业的任务目标,因此对于线程安全的实现仅仅使用了Java自带的synchronized
。
需要进行线程同步保护的类都是用于在线程类之间交换信息的类,在我的设计中,只有负责传递请求的RequestQueue
和传递电梯注册信息的ElevatorInformation
需要进行同步。对于RequestQueue
,其内部维护了一个List<Request>
,对外的put
方法和get
方法均需要同步,此外,这个类还需要在线程间传递输入结束的信号,因此isEnd
属性对应的getter
和setter
也需要同步。对于ElevatorInformation
,其保存的各类电梯信息的getter
和setter
也需要同步。
调度器设计
本单元作业的调度器职能聚焦于将输入的请求派发给各个电梯,至于电梯如何接受、运转、放出各个请求则交由电梯本身来处理。这种调度器架构对于静态的、属性固定的电梯系统很合适,但是如果电梯可以动态添加,其属性可以自定义变化时,这种调度器就显得力有不逮。这个历史遗留问题还是因为早期设计时没有考虑太多,迭代的时候尝试重构但没有成功,只能小修小补。
从架构层面来说,设计中主要由三类线程,输入线程、调度线程和电梯线程,并且每一个电梯的待完成请求队列是独立的。作为中介的调度线程从输入线程中获取请求,根据当前的电梯信息和请求本身进行判断,分发给不同的电梯。第二次作业中由于存在动态添加电梯的指令,因此调度器需要记录同一个楼层\楼座中有哪些电梯(只需要保存电梯id)。在第三次作业中,由于横向电梯的停靠楼座各不相同,因此调度器还需要这方面的信息来判断当前的请求应当被分发到哪一个电梯才能被正确的执行。
架构模式
第一次作业
第二次作业
第三次作业
UML时序图
三次迭代过程中的核心结构基本上没有太大的变化,都是将输入,调度和电梯均作为线程进行处理。第一次作业最为简单,基本上就是上述的核心三个线程与一些共享数据类。在第二次作业中因为需要处理环形电梯,故将电梯运行的逻辑抽象出来,将判断电梯是否停靠与进出的函数实现下放到具体的环形电梯CircuitElevator
和垂直电梯VerticalElevator
中。此时的调度器由于需要当前的电梯状态信息,所以添加了ElevatorInformation
类作为共享对象进行信息传递。第三次作业的大体架构几乎与第二次作业一致,但是由于乘客请求有可能需要被拆分,也就是说有可能无法在一次电梯运送过程中完成请求,因此添加了一个SubRequest
类用于存贮子请求。
就扩展能力而言,当前的架构设计还是有一定的不足的。一方面,电梯运行流程的抽象与解耦不够彻底,对于具体电梯的设置是由若干个判断函数联合起来组成的,有一定的复杂性。如果添加更多类型的电梯,代码的改动不会很轻松。此外,对于线程协作,因为调度器本身也是一个线程,同时为了分发请求到某个特定的电梯,其还需要获得实时的电梯信息,这就要求输入线程与调度线程之间除了用于传递请求的共享队列外,还需要共享一个电梯信息类。如果能够将调度器变成一个单例的共享数据对象,直接对接输入线程和电梯线程,将请求的分发封装在内部,感觉会更为简洁。
bug分析
自己程序的bug
本单元作业中没有被公测与互测检查出bug。
发现bug的策略
hack的思路主要有两类,一类是寻找线程不安全造成的程序错误,一类是寻找由于策略设计不足导致的超时。对于前一类,可以通过构造大量随机的数据进行黑盒测试。对于后一类,则可以通过卡在输入时限结尾传入大量请求来进行测试。
测试策略差别
由于第二单元引入了多线程,测试过程比第一单元复杂许多。这主要是因为线程调度策略对于程序员不可见。因此找到线程不安全的地方不代表能很直接的构造出能够测出线程不安全的数据。通过借助自动化黑盒测试的手段才能够较为方便的进行测试。
心得体会
从线程安全角度,主要学习到了使用同步块和锁对代码进行保护,此外,还可以利用诸如单例等设计模式构建线程安全的类。
从层次化设计的角度,进一步巩固了在第一单元学到的层次设计知识,通过更深入的实践来自己决定如何在早期开发中进行良好的可拓展设计,如何在迭代过程中将共用逻辑抽象出来。