OO第二单元博客总结

郭俊杰 -22371240 学生 2024-04-20 14:34:00

hw5

UML类图

img

UML协作图

img

架构分析

  • Main类:用于创建并开启Input线程,Schedule线程和6个Elevator线程。
  • RequestQueue类:定义了ArrayList<PersonRequest> requests变量存储请求列表,同时定义了共享变量的读写操作,用于实现多线程之间的同步与互斥。
  • InputThread类:同于读取请求,与Schedule线程共享一个RequestQueue变量,将读取的请求加入其中。
  • Schedule类:同于将读取的请求分配给电梯,分别与六个电梯线程共享一个RequestQueue变量。本次作业中由于请求指定了电梯,故直接按请求分配即可。
  • Elevator类:为本次作业核心部分,承担了将乘客运送到目的地的任务。定义OVER, WAIT, MOVE, REVERSE, OPEN这五种策略并定义strategy()函数根据电梯当前状态返回下一步策略。电梯线程只需要通过strategy()获取策略并执行相应的动作。

电梯运行策略

if (canOpenForIn() || canOpenForOut()) {
    return Strategy.OPEN;
} else if (getPasNum() != 0) {
    return Strategy.MOVE;
} else {
    if (waitQueue.isEmpty()) {
        if (waitQueue.isEnd()) {
            return Strategy.OVER;
        } else {
            return Strategy.WAIT;
        }
    } else {
        if (notReverse()) {
            return Strategy.MOVE;
        } else {
            return Strategy.REVERSE;
        }
    }
}

采用look算法,先判断是否开门,若电梯内有目的地为该层的请求,或该层的等待队列中有同向的请求且电梯内可以接受请求,则返回``OPEN开门。若不能开门则判断电梯内部是否有请求,若有则返回MOVE继续移动,否则判断等待队列若为空且且为end(表示调度结束)则返回OVER结束线程,若不为end返回WAIT表示等待。若等待队列不为空,则表示还能继续运行,判断电梯前方的楼层是否有等待的请求,若有则继续移动,否则表示请求在相反方向,返回REVERSE调转方向。

同步块和锁的选择

  • RequestQueue类中所有对共享变量的操作上加锁,加锁对象为RequestQueue对象本身,在getAndRemove()函数获取请求时若为空则等待,在add()setEnd()函数中会进行唤醒。
  • 在执行OPEN策略时使用wait()来实现,当在等待电梯关门的过程中如果能接收新的请求的话则唤醒电梯接收请求。因此,Elevator中执行OPEN的函数与Schedule类中分配电梯的部分设为同步块,使用电梯对象作为锁。
  • 同时为了实现量子电梯,在执行OPEN时使用wait()实现,在电梯移动的过程中如果有位于电梯出发层的请求则电梯重新开门接收请求。因此,Elevator中执行MOVE的函数与Schedule类中分配电梯的部分设为同步块,同样使用电梯对象作为锁。

hw6

UML类图

img

UML协作图

img

架构分析

本次作业修改或新增的类有:

  • RequestQueue类:改用HashMap<Integer, ArrayList<PersonRequest>> requests,按照起始楼层存储对应的请求列表,这样方便电梯线程接受请求的操作。
  • Elevator类:改用HashMap<Integer, ArrayList<PersonRequest>> taskQueue按照抵达楼层存储对应的请求列表,这样在后续实现电梯进出乘客的功能时可以更快速的找到相应的请求。由于在重置中的电梯不能接受请求,因此定义ArrayList<PersonRequest> waitForReceive变量,当电梯重置时先将分配的变量加到此
    列表中,在重置完成后再将该列表中的请求加入到等待队列中,并输出接受信息。
    本次作业再Input线程完成读取后不能立刻将Schedule的请求队列setEnd(),考虑若Schedule中请求队列为空,而还有电梯正在重置,需要将电梯的请求再返回到调度器中重新调度,若此时Schedule线程已经结束则无法进行重新调度。我的解决方法是在Input线程读取完后再等待2s, 这样即使最后一个请求是reset请求也会等到电梯重新分配完再结束Schedule线程。

调度策略

在分配请求时,我选择根据电梯到达当前请求所需时间、电梯等待队列和电梯内部人数、容量以及是否重置这四个点计算每个电梯的cost值,选择代价最小的电梯进行分配。在计算电梯的cost值时,首先考虑电梯到达当前请求的时间,若电梯为运动状态且方向与当前请求与电梯的方向相反,则考虑电梯到达最远端楼层并折返回来的时间。若电梯运行方向相同则计算电梯直接到达该请求的时间。需要注意,在电梯为等待状态和重置状态且没有分配请求时方向是可以改变的,因此此时计算到达时间时都按照电梯直接到达该请求来计算,并将电梯方向设为此方向。而在后面的请求到来时则需要考虑电梯的方向进行计算。还需要考虑电梯等待请求的数量与电梯容量,若总请求数小于电梯容量则cost值不变,否则乘上一定的权重加到cost。这样做,在请求数量少时可以只使用少数电梯完成请求,减少耗电量,而在请求数量多时也不会出现大量请求分配给少数电梯的情况,更加合理。同时考虑到电梯完成重置需要一定的时间,因此给重置状态电梯的cost加上一定的偏移。
这种调度方案虽然计算没有影子电梯准确,但是实现起来比较简单,而且最后的强测中性能分也都比较高。

同步块的设置和锁的选择

除了RequestQueue类中的同步块与hw5中相同外,还新增了以下同步块:

  • Input类中设置电梯为重置状态的函数与Schedule类中计算所有电梯cost值并选择调度电梯的函数设为同步块,这两个同步块的锁是同一个对象。防止在计算电梯cost时前面计算过的电梯发生重置,导致调度选择的结果出错。
  • 同时当Schedule类中调度函数计算发现所有电梯都处于重置状态时加锁等待,在电梯重置结束时加锁唤醒,这两个同步块的加锁对象相同。

hw7

img

UML类图

UML协作图(本次协作图)

img

架构分析

架构与hw6基本一致。
本次作业新增了双轿厢电梯重置。由于在双轿厢重置时需要增加新的电梯,因此我将在Main类中定义的电梯列表改为Elevator类的一个静态变量,这样方便电梯进行重置时向其中增加新的电梯。在电梯类新增Elevator类变量存储双轿厢电梯中另一个电梯,并且者两个电梯不共用同一个等待队列。
在实现双轿厢中两个轿厢不碰撞中,为每个电梯设置一个flag变量判断该电梯是否正在前往换成楼层或正处于换成楼层。当其中一个电梯执行MOVE策略且目的地为换成楼层时,判断另一个电梯如果flag有效,则阻塞等待,否则将flag设为有效并继续移动。当电梯离开换成楼层时将flag设为无效并唤醒另一个正在等待的电梯。当电梯执行WAIT且处于换成楼层时,让电梯移动一层,这样防止另一个电梯要进入换乘楼层时等待时间过长。
此外,本次作业线程的结束条件也需要改变。由于电梯不共用等待队列,当双轿厢电梯中一个电梯还在运行,另一个电梯没有请求时结束线程,这样当还在运行的电梯将请求送到换乘层时将没有电梯来继续完成请求。因此双轿厢电梯结束的条件为,当前电梯没有请求,且同一轿厢中另一个电梯处于等待状态或已经结束。这说明两个电梯都没有请求需要处理,且当等待队列的end有效时即可结束线程。在结束线程时,需要将另一个等待中的电梯唤醒,使其也结束线程。

同步块的设置和锁的选择

本次作业除了与hw6中相同的同步块外,还新增了以下同步块:

  • 由于Schedule类中遍历电梯列表的部分与Elevator中实现双轿厢重置新增电梯的部分都对共享对象elevators进行读写操作,需要加锁使其变成同步块。
  • 双轿厢电梯中当一个电梯需要前往换乘层且另一个电梯处于换乘层时加锁等待,当电梯离开换乘层时加锁并唤醒等待的电梯。

稳定和易变的内容

  • 稳定的内容:
    • 整体架构,均采用生产者消费者模式,由Input线程读取请求,Schedule线程分配请求,Elevator线程完成请求。
    • 单个电梯的运输策略,在hw5中确定后保持不变
    • Schedule调度的实现:都是根据不同电梯对当前请求cost值的比较来选择分配电梯
  • 易变的内容:
    • 电梯的状态:在hw6中新增的重置,hw7中新增的双轿厢电梯的重置,都需要添加新的状态并实现对应状态的动作。
    • 电梯计算cost值的方法:hw5是直接根据请求给定的电梯,hw6中需要考虑电梯的当前状态,hw7中还需要考虑新增的双轿厢电梯。
    • 线程结束的条件:hw5中输入完毕即可结束。hw6中由于电梯重置还需重新调度,不能在输入完毕后直接结束Schedule线程。hw7中双轿厢电梯的结束,一个电梯不能在没有乘客时立即结束,需要等待两个电梯都没有乘客时才能结束。

bug分析

  • hw6中,由于最初的版本调度时没有考虑给重置状态的电梯分配请求,导致互测时面对先重置5个电梯,再读取请求的数据时将所有请求分配给一个电梯,从而导致超时。
  • hw7中,在双轿厢电梯为WAIT状态且处于换乘层时,需要设置电梯方向并使其移动一层。而在计算cost的函数中当电梯为等待状态且无请求时会改变其方向。由于这两处没有设置为同步块导致电梯可能朝相反方向移动,从而超过换乘楼层导致出错。
  • debug方法:由于多线程无法采用断点调试,因此debug时会比较费劲。我在写代码时出现的bug多是由于线程死锁导致。我的方法是在所有可能出现死锁的同步块中的wait()函数前后print信息,然后根据运行时打印的信息来锁定程序是卡在了哪些同步块中。

心得体会

线程安全

通过完成这一单元作业,我认为在多线程程序的编写过程中要保证线程安全,最重要的是保证对共享数据访问的互斥。需要找出程序中所有的共享变量并设置同步块保证线程互斥。正如本次作业中线程的共享对象不只有RequestQueue这一个变量,还有elevator对象为Input线程与Schedule线程的共享对象,elevators存储电梯列表的变量为Elevator线程与Schedule线程的共享对象,以及Elevator类中的某些属性也为共享变量,在程序编写中需要全面地思考并找到这些共享对象才能保证整个程序的线程安全。

层次化设计

  • 请求处理:
    • 读取请求:通过Input线程读取请求并交给Schedule线程
    • 分配请求:通过Schedule线程分配请求给电梯
    • 完成请求:通过电梯线程实现乘客的运送
  • 电梯内部实现:
    • 提出策略:strategy()函数根据当前电梯状态给出电梯下一步的策略
    • 执行策略:电梯只需要执行当前的策略即可
      通过层次化的设计完成对请求的处理,将复杂的任务划分给几个模块,各个模块结构功能清晰,各司其职。在后续迭代过程中只需要根据新需要更改相应的模块即可。
...全文
47 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

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

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