BUAA OO 第二单元总结

目录
  • OO 第二单元
    • 一、设计策略
    • 二、可扩展性分析
    • 三、程序结构分析
    • 四、bug分析
    • 五、互测策略分析
    • 六、心得体会

OO 第二单元

多线程的经验和教训

一、设计策略

这三次作业大致采用了相同的策略。

线程设计方面,都是一个Input(输入器)线程,一个Controller(控制器)线程,若干个Elevator(电梯)线程。交互方式上,主要通过锁来进行同步控制。每部电梯有一个自己的privateLock,这些privateLock对控制器也可见;同时控制器和所有电梯共享一个publicLock,控制器和输入器共享一个inputLock。(第一次作业中,由于只有一部电梯,电梯与控制器之间只共享一把锁。)

容器方面,前两次作业是控制器和输入器共享一个list,每部电梯有自己的list;第三次作业是控制器对应A, B, C三种电梯有三个list,每部电梯有自己的list.

请求封装成Person对象,属性有from, to, id, inElevator(是否在电梯内)。

线程运行方面:

  • 电梯线程根据自己的主请求人(list.get(0))决定要上行还是下行。主要逻辑是如果主请求人不在电梯内,去接主请求人;如果主请求人在电梯内,去送主请求人;特别地,第三次中如果主请求人需要换乘,则前往1或15层中比较近的一个放下主请求人(将他送回控制器list,且from楼层改为当前楼层)。电梯线程每移动一层,就通过publicLock叫醒控制器,然后等待privateLock。特殊情况是电梯list为空时等待privateLock.
  • 控制器每次被叫醒时,首先通过inputLock叫醒输入器,然后等待inputLock。接下来尝试获取每部电梯的privateLock,如果获得则说明该电梯为等待状态。对于等待状态的电梯,如果该电梯为空,给该电梯分配一个最近的请求;然后分配可捎带的请求。特殊情况是控制器和电梯的list全部为空时,等待inputLock.
  • 输入器每次被叫醒,向控制器添加请求,然后通过inputLock叫醒控制器。
    回到目录

二、可扩展性分析

  • 为了实现简单,本次电梯使用可稍带算法,可以通过修改算法获得更好的性能。
  • 乘客可以有优先级,同样通过修改分配算法实现。
  • 电梯运行考虑加速度/电梯开关门时间根据人数变化,可以通过修改电梯的相关运行方法实现。
    设计原则检查:
  • SRP: 基本满足,电梯和控制器各司其职,内部每个方法的工作量适中。
  • OCP: 做得不好,每次都是直接修改之前的代码,以后的作业里可以更多考虑使用继承来实现。
  • LSP, ISP, DIP: 由于本次作业继承和依赖关系比较简单,不太能体现这些原则。
  • 显式表达原则:用0位置隐含表达了主请求,可以用一个常量代替。
    回到目录

三、程序结构分析

由于三次作业设计类似,为了避免冗余,以第三次作业为例进行分析。


作业3行数统计

BUAA OO 第二单元总结_第1张图片
作业3method

BUAA OO 第二单元总结_第2张图片
作业3class

BUAA OO 第二单元总结_第3张图片

作业3主要部分UML

BUAA OO 第二单元总结_第4张图片
作业3UML时序

  • 无论是通过行数、复杂度或是UML图,都可以看出Controller和Elevator是绝对的主角,这是由于任务本身决定的。而Controller和Elevator的任务量大致相当,我认为是比较不错的现象。
  • 具体到方法上,可以说也是比较不错的,没有出现特别复杂的方法。相对比较复杂的方法除了run之外,还有Controller的addPerson(需要判断加入A, B, C三个队列的哪一个),allocate2Elevator(需要根据电梯种类以及是否为空进行分配),Elevator的judgeOpen(判断是否需要开门),move(判断当前需要运行的方向并运行)。
    回到目录

四、bug分析

第一次作业强测爆0,后两次作业未被测出bug,这里着重分析一下第一次作业。

第一次作业的问题主要在于没有分清synchronized(lock)和lock.wait()的区别,把synchronized(lock)没拿到锁时也理解成了waiting状态。这就导致电梯每运行一层,本应该把锁交给控制器,但是有可能再次交给自己从而导致捎带失败。值得一提的是,由于这个bug的触发是有一定随机性的,所以在我的简单测试中没有显现出来。周六下午我通过进一步测试发现了这个问题,但还是由于锁机制理解不到位没能成功修复,最后只能抱着没准能过的想法提交,果然得到了一个0分。
这次的bug一定程度上是因为首次接触多线程程序不太适应,也给了我一些启示。首先,在拿到一个新东西的时候,如果感觉某个理论点存在理解上的模糊,一定要彻底弄明白再应用。其次,多测试、早测试,对于多线程程序来说更是如此,这次做得还是不够好。最后,我充分理解了多线程程序“通过测试数据不代表没bug,没通过一定有bug”的特点,在后续评测机报错而本地无法复现的时候,可以安心debug而不是怀疑评测机。

此外,在提交中测和自己测试的过程中,主要遇到过两个问题。第一个是某个lock在lock.lock()之后,进行了一个分支语句,在else分支的最后忘了lock.unlock(),这一点在进行手动锁控制的过程中需要尤其注意。 第二个是两个线程同时notify对方,然后同时wait被对方notify,形成死锁,可以通过设定wait的时间上限或者单开一个线程定时notify来解决。
回到目录

五、互测策略分析

由于感觉这一单元的作业相对比较难,所以我花在自动测试上的时间较少。我只编写了一个定时投放数据的脚本(因为如果没有这个脚本几乎无法进行有效测试),而没有写数据生成和正确性判断的脚本。
第一次作业因为惊天大bug没有进入互测。第二次作业因为没有自动评测程序,有些自暴自弃,没太认真参与互测。但是第二次作业的互测结束后,我查看了其他同学hack成功的数据点,发现大家的数据都是手搓的而不是自动生成的。所以第三次互测我设计了5组数据,类型是:最后一条是换乘请求,测试是否因为提前关闭电梯导致最后一名乘客无法到达;电梯全部空闲一段时间后再输入请求,测试是否会有相关的线程交互问题;50条指令同时到达的压力测试;乘客请求与加电梯请求同时到达的线程安全测试;以加电梯请求结束的线程安全测试。让我非常惊讶的是,这5组数据每一组都hack成功,且总共hack中7人次,这说明多线程的线程安全的确非常容易出问题。
回到目录

六、心得体会

  • 这一单元的作业是我首次接触多线程程序,对多线程的设计思想有了初步的感觉。我感觉多线程中的每个线程对象可以看作是一个“活的”对象,它有自己的想法,同时也受到一定规则的约束。正是因为这样的特点,多线程可以更好地模拟实际生活中的场景和需求,简直太巧妙啦!当然,相应地,这也带来了麻烦。如果线程运行的规则没有设计好,线程就可能会做出一些傻事。设计模式中蕴含了大家对于线程控制思想的总结,非常值得深入学习。总的来说,线程交互是一个非常深奥的问题,在未来需要进一步的学习和思考。
  • 与第一单元相比,本单元作业体现出了迭代开发的特点,未经重构,这一点是我比较满意的。

你可能感兴趣的:(BUAA OO 第二单元总结)