Buaa_OO_二单元总结

本文介绍了多线程实时电梯系统的实现,包括上下行、开关门等功能,并通过迭代进行优化。文章详细阐述了架构设计,如辅助类、主线程、守护线程、输入线程、调度线程和运行线程的职责。此外,还讨论了调度器的设计,同步块和锁的设置,以及bug分析和调试策略。在每次迭代中,系统增加了新功能,如电梯扩建、维护调度和可达性支持,并针对性能和线程安全进行了改进。
摘要由CSDN通过智能技术生成

Buaa_OO_二单元总结

题目背景

一个多线程实时电梯系统。

系统从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出。

hw1

  • 实现功能
    • 上下行,开关门,以及模拟乘客的进出。

hw2

  • 实现功能
    • 上下行,开关门,模拟乘客的进出;
    • 模拟电梯系统扩建和日常维护时乘客的调度。

hw3

  • 实现功能
    • 上下行,开关门,模拟乘客的进出;
    • 模拟电梯系统扩建和日常维护时乘客的调度;
    • 新增的电梯可达性支持自定义;
    • 任意楼层设置电梯服务许可限制。

架构设计

辅助类

  • Person

    对官方 .jar 包中的 PersonRequest 进行封装,在官方包的基础上增加了在 Person 中自定义 PersonRequestfromFloortoFloor 的功能。

  • RequestQueue

    储存指令的队列。

  • Elevator

    储存电梯的基本参数。

  • AllState

    储存电梯的实时运行状态。

  • StateElevator

    为电梯的运行状态提供一个统一接口。

  • Waiting

    • 等待状态1
    • 操作: 无。
  • Loading

    • 即将运行状态1,介于 WaitingArrive 之间。
    • 操作:无。
  • Arrive

    • 抵达状态1
    • 操作:执行 ARRIVE OPEN IN OUT CLOSE 操作。
  • Running

    • 运行状态1
    • 操作:改变电梯当前楼层。

主线程

  • Main

    构造辅助类及依次启动守护线程、运行线程、调度线程与输入线程。

守护线程

  • Daemon

    当所有运行线程都结束时,不断对调度线程中等待队列进行notifyAll

输入线程

  • InputThread

    读入指令。

调度线程

  • DispatchThread

    将读入的指令分配给相应的运行线程。

运行线程

  • OperateThread

    依次执行调度线程分配的指令。

迭代分析

基于UML类图的迭代分析

hw1

hw2

  • 新增 Person 类,继承 PersonRequest ,对官方 .jar 包中的 PersonRequest 进行转储,在官方包的基础上增加了在 Person 中自定义 PersonRequestfromFloortoFloor 的功能。

  • 输入线程 InputThread

    • 新增对每个读入指令具体类型的判断,并进行相应的操作。

      • 若为 PersonRequest ,则构造 Person 储存 PersonRequest 中的数据;

      • 若为 ElevatorRequset ,则调用 DispatchThread.addElevator()

      • 若为 MaitainRequest ,则调用 DispatchThread.maintainElevator()

  • 调度线程 DispatchThread

    • 调度策略的改变,见调度器设计

    • 新增静态方法 DispatchThread.addElevator()

      DispatchThread.addElevator()

      新建电梯类,增加与其对应的运行进程。

    • 新增静态方法 DispatchThread.maintainElevator()

      DispatchThread.maintainElevator()

      将相应电梯所对应的运行进程的维修信号 signalMaintaintrue

  • 运行线程 OperateThread

    • 随调度策略做出的改变,见调度器设计

    • 新增对维修信号 signalMaintain 的判断;

    • 新增静态方法 OperateThread.maintainElevator()

      OperateThread.maintainElevator()

      • 释放运行进程处理队列中的指令到等待队列;
      • 释放运行进程装载队列中的指令到等待队列,并修改指令的起始楼层为电梯当前所在楼层。
      • 执行所需的 OPEN OUT CLOSE 操作。
hw3

  • 新增守护线程 Daemon ,解决了一些情况下的轮询问题。

    • 当所有运行线程都结束时,不断对调度线程中等待队列进行notifyAll
  • 输入线程 InputThread

    无需改变。

  • 调度线程 DispatchThread

    • 引入直达邻接矩阵 direactArrival[11][11](以直达需要经过的楼层数为权),当有 ElevatorRequestMaintainRequest 时,及时更新该矩阵;
    • 调度策略的改变,见调度器设计
    • 新增两个 Semaphore[] ,分别用来储存每个楼层的电梯的服务许可和每个楼层的电梯的只接人服务许可。
  • 运行线程 OperateThread

    • 在执行 OPEN 操作时,新增获取相应许可的操作;
    • 在执行 CLOSE 操作时,新增释放相应许可的操作。

基于UML协作图的迭代分析

hw1

hw2

hw3

调度器设计

hw1

  • 调度线程结束条件

    • 等待队列为空 && 等待队列 isEnd 标记为真。
  • 调度策略:“捎带“

    若从等待队列中获取指令不为 null ,则遍历全部未结束的运行进程,若电梯处于Waiting 状态,则执行 addPassenger() ;否则,若电梯未满,则执行 pickPassenger()

    • addPassenger()
      • 获取等待队列的队头指令,将其加入该运行进程的处理队列;
      • 遍历等待队列,获取其余指令。若该电梯未满,且该指令与此次调用 addPassenger() 加入的第一条指令起始楼层与前进方向分别一致,则将其加入该运行进程的处理队列。
    • pickPassenger()
      • 遍历等待队列,若该电梯未满,且该指令前进方向与电梯当前前进方向一致,且其出发楼层在电梯的途经道路上,则将其加入该运行进程的处理队列。

hw2

  • 调度线程结束条件

    • 所有运行线程均结束。

      运行线程结束条件(下述条件关系为 ||

      • 当前电梯的维修信号 signalMaintain 为真 。
      • 等待队列为空 && 所有运行线程的(处理队列为空 && 装载队列为空)&& 当前运行线程的处理队列 isEnd 标记为真。
  • 调度策略

    无变化。

hw3

  • 调度线程结束条件

    无变化。

  • 调度策略:“能捎则捎的贪婪匹配“

    若从调等待队列中获取指令不为 null ,则遍历全部未结束的运行进程,若电梯处于Waiting 状态,则执行 addPassenger() ;否则,若电梯未满,则执行 pickPassenger()

    • addPassenger()

      • 遍历等待队列,获取其中指令。调用 updataFloorTo() 更新该指令的目标楼层。

        updataFloorTo()

        • 若存在原始电梯,则 return false 更新失败;
        • 若不存在原始电梯,则利用迪杰斯特拉算法求出该请求在当前直达邻接矩阵 directArrival[11][11] 下的最短路径,并将该请求的目标楼层更新为最短路径需要到达的第一个目标楼层。 return true 更新成功。
        • 更新失败:若该电梯未满,且该电梯可将该指令运输到离目标楼层更近且不越过目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令与此次调用 addPassenger() 加入的第一条指令起始楼层与前进方向分别一致,则将其加入该运行进程的处理队列。
        • 更新成功:若该电梯未满,且该电梯可将该指令运输到目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令与此次调用 addPassenger() 加入的第一条指令起始楼层一致、前进方向一致,则将其加入该运行进程的处理队列。
    • pickPassenger()

      • 遍历等待队列,获取其中指令。调用 updataFloorTo() 更新该指令的目标楼层。
        • 更新失败:若该电梯未满,且该电梯可将该指令运输到离目标楼层更近且不越过目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令前进方向与电梯当前前进方向一致,且其出发楼层在电梯的途经道路上,则将其加入该运行进程的处理队列。
        • 更新成功:若该电梯未满,且该电梯可将该指令运输到目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令前进方向与电梯当前前进方向一致,且其出发楼层在电梯的途经道路上,则将其加入该运行进程的处理队列。

同步块和锁的设置

  • 采用 synchronized(Object) 对对象加锁;

  • 采用 synchronized 标记对方法进行同步。

hw1

  • 对指令队列、每个电梯的实时状态 Allstate 加锁;
  • 对指令队列的修改方法进行同步。

hw2

无变化。

hw3

  • 对新增的直达邻接矩阵 directArrival[11][11] 加锁;
  • 对新增的守护进程 Deamon 中的方法进行同步。

Bug分析

出现的bug

  • MaintainRequest 执行不及时

    将指令类型的判断从调度进程 DispatchThread 提前至输入进程 InputThread

  • CPU_TIME_LIMIT_EXCEED

    减少了不必要的 NotifyAll(),在一些情况下新增了 wait() ,同时增加守护进程 Deamon ,实现在运行进程 OperateThread 全部结束后唤醒调度进程 DispatchThread

debug策略

  • 在每个线程结束后输出提示语句;
  • 在分配指令进电梯时输出提示语句;
  • 利用 JProfiler 工具分析死锁;
  • 利用 JProfiler 工具查看每个进程的运行时间及CPU时间,针对性地进行优化。

心得体会

经过三次的迭代开发,一个多功能多线程的电梯系统呈现在我们面前。

  • 确保线程安全是多线程程序设计中至关重要的问题。
    • 在不断的学习与探索中,我明确了死锁的形成原因、如何以及何时上锁,可以合理确定上锁的范围,合理使用 wait-notify 来代替轮询达到降低CPU负载的目的;
    • 多线程往往会造成一些不可复现的bug,此时就需要仔细分析代码的运行逻辑,在纷繁复杂的表象背后寻找可能造成问题的蛛丝马迹,达到在根本上解决问题的目的,而不可以依赖多线程的不确定性来通过测试。
  • 层次化设计是面向对象程序设计中必不可少的关键一环。
    • 适当的抽象可以提高程序的可阅读性,增加程序的可拓展性,为迭代开发提供便利;
    • 此次项目,我采用了生产者-消费者模式、状态模式,在一定程度上实现了层次化设计。

最后对老师以及助教们的付出致以衷心的感谢!


  1. 电梯状态。 ↩︎ ↩︎ ↩︎ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值