关于“OO第二单元总结”这件事

22371207石伊聪 学生 2024-04-20 12:16:33

说是第二单元,其实是五、六两次作业,第七次没写完所以寄了。

概览

因为上面的原因,以hw6来举例说明我的实现的整体架构。

PS C:\some\random\path\to\src> tree /F .
.
        Controller.java
        Elevator.java
        ElevatorQueue.java
        ElevatorState.java
        Input.java
        MyPersonRequest.java
        Program.java
        Queue.java
        Scheduler.java

我设计的类包括:

  • 主类,负责开启输入线程和调度器线程
  • 输入类,接受输入,通过官方包将其转化为请求
  • 调度器类,开启(电梯)控制器线程,将请求按策略分配给控制器
  • 控制器类,负责控制电梯的上下运转和重置等基本功能
  • 电梯类,负责输出和休眠

将电梯和控制器分离属于预测错误导致的设计失误。这样设计原本是为了适应像往年的”横向电梯“场景,在”双轿厢电梯“场景这样的设计反倒增加了复杂性。

协作图

img

同步块与锁

我在hw5使用了同步块,hw6和hw7换成手动加锁,避免了搞笑的缩进。

hw5时由于我对同步块的机制不够了解,给Controllerrun()方法的主循环加了同步块,导致无法捎带,造成了很严重的性能问题。

注意ElevatorQueueQueue两个类,在hw6和hw7中,我将加解锁的逻辑全部移动到这两个类中,很大地降低了心智负担。

调度器设计

调度策略

随机策略。很敷衍,但是当你发现随机策略比自己“精心”设计的调度策略表现更好的时候,保持理智是一件很难的事情。

从性能分上看,随机策略是比较差的,但不是最差的调度策略。

至于耗电量,也许我们可以发布一份调查问卷,调查究竟有多少人在意过这个耗电量。

与其他线程的交互

  1. Input

    只需要单方面接受输入即可。两个线程共同持有Queue<Request>的引用。

  2. Controller

    1. 分配乘客。Scheduler将一个请求加入Controller的队列,并notify这个队列
    2. 重置。Controller将队列中的请求加入Scheduler的队列

电梯的策略

采用了指导书推荐的ALS策略,中规中矩。

稳定和易变内容

稳定的:主类,输入,队列,调度策略,电梯运行策略

易变的:电梯本身

防止两个轿厢打架

我手动实现了一个类似互斥锁的结构,让两个轿厢控制器持有该结构的引用。

当一个轿厢试图进入换乘层时,它先检查锁是否被占用,若是,它notify另一个线程并等待。它成功进入换乘层时先获取锁,离开后释放锁。

此外,我规定当轿厢进入换乘层之后,需要尽快离开,即使当前不再有请求。

给双轿厢电梯只分配一个线程也可以防止冲突,但会导致令人担忧的性能问题

Bug和Debug

我发现的自己的bug:

  1. 死锁,相当常见,一般是因为有的方法忘记notify()
  2. 忙等(轮询),有的while循环没有wait()导致的

我发现的别人的bug:

  1. 多线程读写,容器类没加锁导致的ConcurrentModificationException

  2. 分配策略不合理

    很多人在hw6都受到了这则数据的洗礼:

    [1.0]100-FROM-1-TO-11
    [10.0]RESET-Elevator-6-3-0.6
    [49.0]RESET-Elevator-1-3-0.3
    [49.0]RESET-Elevator-2-3-0.3
    [49.0]RESET-Elevator-3-3-0.3
    [49.0]RESET-Elevator-4-3-0.3
    [49.0]RESET-Elevator-5-3-0.3
    [49.9]10-FROM-10-TO-11
    [49.9]70-FROM-9-TO-11
    ...
    

    因为其余电梯都在重置,不好的调度策略会导致乘客在6号电梯门口踩踏,场面一度非常混乱

    我自己也中招了

Debug

对于死锁问题,我使用调试功能查看死锁线程的调用栈,从而定位死锁原因

对于错误的分配策略,有两种解决方案:一种是设置当有过多数量的电梯重置时暂停分配,另一种是为电梯增加buffer。

我选择了前者,因为可以压到五行以内 :

import java.util.stream.IntStream;
if (IntStream.rangeClosed(1, elevatorCount)
        .filter(i -> getController(i).isResetPendingOrResetting()).count() >= 4) {
    requestQueue.await();
}

心得体会

线程安全:一句话概括: 涉及多线程读写要加锁

层次化设计:实话讲对本单元我在这一点上做得很烂,原因开头讲了

第七次没写完所以寄了。

将电梯和控制器分离属于预测错误导致的设计失误。

但是做没做呢?“将电梯和控制器分离”也是一种层次化,只是设计方向与需求不匹配,反而拖了后腿。

如何平衡代码设计、未来可能的增量和模块化额外消耗的精力,是一个值得思考的问题。

花了四周时间,在多线程这块算是入了门,正巧OS那边也学到多进程、多线程的内容,相信这会让我对多线程程序设计与更深的理解()

...全文
32 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧