OO Unit2 总结

OO Unit2 总结

目录

  1 程序结构分析

    1.1 第一次作业

      1.1.1 设计策略

      1.1.2 UML

      1.1.3 Metrics

      1.1.4 可扩展性

    1.2 第二次作业

      1.2.1 设计策略

      1.2.2 UML

      1.2.3 Metrics

      1.2.4 可扩展性

    1.3 第三次作业

      1.3.1 设计策略

      1.3.2 UML

      1.3.3 Metrics

      1.3.4 可扩展性

  2 bug分析

    2.1 第一次作业

    2.2 第二次作业

    2.3 第三次作业

  3 心得体会

1 程序结构分析

1.1 第一次作业

1.1.1 设计策略

  设计的类包括:

  1. 两个基本数据类:Person、Elevator

    Person维护一个乘客的所有信息,不可更改。

    Elevator维护一部电梯的所有信息,提供open、close、moveUp、moveDown方法支持电梯的基本操作。

  2. 两个线程安全类:Persons、Command

    Persons维护一个乘客队列,提供addPerson、getPerson方法支持乘客的同步入队、出队。

    Command维护一个电梯控制界面,包含控制器需要的电梯位置、人数、运行方向等信息,以及电梯需要的等待队列、停靠路线等信息。对这些信息的更新是同步的。

  3. 三个线程:Input、Controller、RunningElevator

    Input负责输入,将输入的请求包装后放入一个Persons。

    Controller负责调度,从Persons取出乘客信息,从Command取出电梯信息,通过调度算法得到新的停靠路线,然后更新Command中的停靠路线,并将该乘客加入等待队列。

    RunningElevator负责运行电梯,从Command取出停靠楼层,从而控制电梯的移动和开关门,并更新Command中的电梯信息。

  两个线程安全类使用synchronized进行同步,保证线程安全。Input和Controller之间通过一个Persons通信,Controller和RunningElevator之间通过一个Command通信。但Command需要承担双向的通信工作,还是有死锁的隐患,这一点在之后的作业中有所改进。

1.1.2 UML

  

1.1.3 Metrics

  方法复杂度

  

  类复杂度

  

  由于第一次将调度算法写在RunningElevator类中,而Controller类只是简单地搬运乘客到等待队列,所以RunningElevator类和Controller类的复杂度明显不平衡,这一点在之后的作业中有所改进。

1.1.4 可扩展性

  这次作业中,考虑到可扩展性的地方集中体现在两点:

  1. 将维护电梯本身数据的Elevator类与运行电梯的线程RunningElevator类分开。这便于对电梯本身的属性和电梯运行方式分别修改。

  2. 在控制电梯的停靠楼层时,使用一个队列维护停靠路线,而不是仅由乘客的起止楼层决定。这主要是考虑到之后可能的换乘,以及便于独立地修改调度算法,而不用修改电梯线程。

1.2 第二次作业

1.2.1 设计策略

  类的设计与第一次完全相同,修改了一些命名。

  保证一个RunningElevator线程通过一个Command与唯一的Controller通信,不同的电梯线程之间没有共享的数据。

  调度算法首先依次更新所有电梯的状态,然后确定乘客进入哪一个电梯的等待队列,最后更新该电梯的停靠路线。其中存在一个问题:当调度器获取完所有电梯更新后的状态,正在计算乘客将进入哪个电梯时,调度器线程已经退出同步区。此时可能有电梯的状态发生改变,使得调度器计算得到的调度策略并不是当前情况下最优的。我在设计过程中发现但忽略了这个问题。一方面,调度器线程获取电梯状态更新时,需要获得Command的锁,从而可能阻塞电梯线程。为了保证并发的效率,不应该让调度器线程同时阻塞所有电梯线程。另一方面,我的调度算法本身就比较粗糙,与最优策略相去甚远,所以没有考虑这一点损失。

  Command类需要双向通信的问题仍未改进。

1.2.2 UML

  

  basic包

  

  synchronize包

  

  thread包

  

1.2.3 Metrics

  方法复杂度

  

  类复杂度

  

  将调度算法移入Controller类之后,RunningElevator类和Controller类的复杂度比较平衡了。但线程类中的算法没有做层次的划分,所有的算法都堆积在线程类中,导致线程类复杂度高。这一问题在之后的作业中并没有改善,反而更加明显了。

1.2.4 可扩展性

  由于类的设计几乎与第一次相同,所以可扩展性方面也几乎没有什么新的设计。

  在第一次作业中,Elevator类的最大人数、楼层范围、运动速度、开关门速度都是静态的。但在第二次作业中,考虑到之后可能有多种电梯,所以让这些属性作为构造方法的参数给出。但是仍然只能支持连续的停靠楼层,所以在第三次作业中没有在第二次作业的基础上扩展,而是重构了。

1.3 第三次作业

1.3.1 设计策略

  第三次作业中进行了重构,但整体模式相似。设计的类包括:

  1. basic包,包含基本数据类

    Person类与之前的Perosn类有区别,增加属性dst,表示当前的目的楼层,可以动态地设置。

    Elevator类为父类,有子类ElevatorA、ElevatorB、ElevatorC,分别表示A、B、C三种类型的电梯。本应该将Elevator设计为抽象类的,但没有这样做。

  2. synchronize包,包含线程安全类

    删去了原先的Command类,设计了抽象类Command,它们除了名称相同外没有任何关系。抽象类Command表示一个单向通信的同步容器。提供terminate方法结束输入。有子类PersonBuffer、ElevatorBuffer、Floor。

    PersonBuffer类与原先的Persons类相同。

    ElevatorBuffer类维护一个新增电梯队列,与PersonBuffer类似。

    Floor类的功能与原先的Command类相似,但不完全相同。Floor类是按照楼层组织的,一个Floor对应一个楼层,对应多个电梯。此外,删除了调度器线程Controller,Floor类通过修改乘客的目的楼层来对乘客进行调度。因此,Floor类实际上承担了调度器的工作,这是因为注意到在换乘的条件下,可以仅由乘客自身决定其路线。当然,这样会损失不小的性能。

  3. thread包,包含线程类

    Input类与原先的Input类相同。

    PersonManager类负责搬运乘客,从PersonBuffer中取出乘客,将其加入到对应的楼层。

    ElevatorManager类负责搬运电梯,从ElevatorBuffer中取出电梯,新增到电梯列表elevators中,并启动一个RunningElevator线程来运行它。

    RunningElevator类与原先的RunningElevator类基本相同,不同的是它需要在乘客到达换乘楼层时修改该乘客的目的楼层。

  Floor类相比原先的Command类不再是双向通信的媒介,因此解决了死锁的隐患。但由于所有的电梯线程共享同一个楼层列表,所以并发效率可能不如原先的Command类要好。

1.3.2 UML

  

  basic包

  

  synchronize包

  

  thread包

  

1.3.3 Metrics

  方法复杂度

  

  类复杂度

  

  可以看出,线程类更加复杂了。

2 bug分析

2.1 第一次作业

  第一次作业时,还没有任何多线程编程的基础,更不必说多线程调试。因此出现了很多问题,最后没有通过弱测。

  第一次作业的bug都是一些多线程的基本问题。由于没有抽象出线程结束的判定模式,所以会出现线程没有结束的情况,导致运行超时;由于对线程间通信不太理解,所以没有使用通知机制,而是轮询,导致CPU超时。

  之所以有这么多bug没能排查出来,主要是因为没有多线程调试的经验,不知道如何搭建自动测试,所以测试只能靠手动测试,局限性很大。

2.2 第二次作业

  第二次作业时,对多线程有了一定的了解,并且有了第一次的教训,因此排除了第一次作业中的很多问题。但还是没有通过弱测。

  第二次作业的bug出现在线程结束的判定问题上。虽然已经有了第一次的经验,考虑了线程结束的条件,并进行了分类。但还是存在疏忽,并且到最后都没能排查出来。一方面是因为在进行分类时考虑不够周全,遗漏掉了一些情况,虽然是很小一部分,但还是在测试中有体现的;另一方面是因为此时仍然依靠手动测试,测试的效果很差,对排查bug几乎没有帮助。

2.3 第三次作业

  第三次作业时,已经搭建了自动测试,不用再依靠评测机debug,姑且通过了弱测。

  第三次作业的bug来源于我自己的懒惰。因为不想设计好的换乘调度算法,而采用了打表的方法。然而在构造换乘表时又出现了问题,这样的问题非常难以排查,因此到最后也没有解决,在测试中体现为有电梯反复运行而无法停止。

3 心得体会

  相比于上一个单元,这一单元的难度可谓上了一个台阶。最大的体会在于多线程与单线程在调试上的差别——多线程调试想要手动调试几乎是不可能的。除了个别边界数据需要手动构造外,想要对并发过程进行调试,就必须要搭建自动测试。

  在今后的学习中,应当提前做好预习工作,避免在这次作业中出现的问题——对新的知识毫无准备,从而漏洞百出。此外,还要克服懒惰,勤于思考和实践,追求更好的效果。

posted @ 2020-04-18 02:49  Somny  阅读(112)  评论(0编辑  收藏  举报