BUAA_OO第二单元总结

七仔^_^ 学生 2024-04-19 22:00:28

OO第二单元总结

hw5

题目要求简述

完成乘客请求,即对于每个乘客请求(起点层,终点层),需要调度电梯将其完成,并将必要的运行信息通过输出接口进行输出。具体而言,你需要控制电梯上下行,开关门的动作以及控制乘客进出电梯来将乘客从起点层运送到终点层。我们的电梯系统有多部电梯,你需要合理的调度这些电梯来达到更好的性能(第一次作业指定了接送乘客的电梯)。

架构设置

UML类图

img

UML时序图

img

调度器设计

本次作业强制乘客登上指定电梯,无需设计调度策略,只需按照电梯号分配乘客。

运行策略

根据往届学长的博客,我选择了LOOK策略

LOOK策略:

  1. 电梯初始化设置楼层和方向
  2. 到达某楼层,首先判断电梯内人是否可出,若可,放出。
  3. 判断本层是否有请求者目标楼层在当前电梯运行方向上且电梯未满载,若有,则捎带。
  4. 若电梯内有人,则继续按照原方向移动
  5. 若电梯内无人,但等待队列有人,且出发楼层与电梯方向相同,则电梯原方向移动
  6. 若电梯无人,但等待队列有人,且出发楼层与电梯方向不同,则电梯转向
  7. 若电梯无人,且等待队列空但未结束,则原地等待请求输入
  8. 若电梯无人,且等待队列空且结束,则结束电梯线程

同步块的设置和锁的选择

本次作业涉及多线程共享访问的对象只有一个:总等待队列(waitQueue),所以将涉及waitQueue的加,删,读取方法加上synchronized语句块即可。
e.g.

public synchronized void addRequest(PersonRequest personRequest) {
        personRequests.add(personRequest);
        notifyAll();
    }

hw6

新增要求

  • 增加reset指令,电梯在运行一段时间以后,其性能参数(满载人数、移动时间)可能会发生改变。接收到重置指令的电梯需要尽快停靠后完成重置动作,再投入电梯系统运行。为安全起见,电梯重置时内部不可以有乘客,且重置动作需要时间1.2s
  • 增加receive指令,分配乘客时需要输出是哪个电梯接收了这个乘客。注:电梯重置时不可接收乘客。

架构设置

UML类图

img

UML时序图

img

调度器设计

由于本次作业我花了大量时间和精力处理reset指令的正确性,所以只采用了性能一般的均分算法。思想是:按照顺序将收到的请求平均分给6部电梯。出现问题:当电梯正在重置时如何分配,receive语句如何输出?
解决思路:在每个电梯内部新建一个队列(我称之为缓冲队列),存储电梯reset时接收的请求。在分配时若电梯在重置,则将请求放入电梯的缓冲队列,等电梯重置结束时输出receive语句。

运行策略

电梯仍采用LOOK策略

同步块的设置和锁的选择

本次代码需要被多线程共享的对象有:总等待队列,电梯的reset信号(被Schedule线程读取和写入,被ElevatorThread线程读取)

  • 总请求队列与hw5的处理方法相同,对方法上锁即可。
  • reset信号,我使用了java自带的原子语句——AtomicBoolean——来表示reset信号。当AtomicBoolean为true时,表示电梯收到reset信号,当AtomicBoolean为false时,表示未收到reset信号。保证只有一个线程正在访问或者修改电梯的reset信号。

bug

  • ctle,遇到ctle后,我发现学长分享了ctle的相关处理方法:在while(ture)循环的各个if分支下输出“********”,然后运行,若“********”行数出现成千上万行,则是ctle。然后依次删除各个分支的*语句,当删除某个分支时,千万行*输出截止,则是这个分支出现了轮询。就此开始debug即可。
  • 本次作业中测最初有两个点神奇地rtle了,时过时不过,发现是死锁问题。在极小的情况下,两个线程会互相访问对方的锁,造成死锁。通常情况如下:
    public synchronized void dosth1(){
        dosth2();
    }
    public synchronized void dosth2(){
        dosth1();
    }
    
    经过努力排查,最终我把所有的嵌套锁全都改成了单层锁,过了。教训:不要随意写锁保证不必要的线程安全,既影响性能,也可能会造成死锁难题。

hw7

新增要求

新增DoubleReset指令,将单轿厢变成双轿厢电梯

架构设计

UML类图

img

UML时序图

img

调度器设计

由于新增reset指令,电梯的状态变成了三个:正常态,normalReset态,doubleReset态。所以我把AtomicBoolean改成了AtomicInteger,分别用0,1,2表示。调度策略仍然是均分策略。

双轿厢相撞问题解决

新增一个类OccupiedFlag,描述换乘层是否被占用。具体细节如下,实现方法参考了讨论区姜涵章同学的分享。注意,这个类实际上起到A和B中的桥梁作用,让二者都可以访问换成层的占有信息。

public synchronized void setRelease(int eleId) {
        this.state = State.UNOCCUPIED;
        //TimableOutput.println("finish wait " + eleId);
        notifyAll();
    }

    private synchronized void waitRelease(int eleId) {
        notifyAll();
        while (state == State.OCCUPIED) {
            try {
                //TimableOutput.println("get to wait " + eleId);
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public synchronized void setOccupied(int eleId) {
        waitRelease(eleId);
        state = State.OCCUPIED;
        notifyAll();
    }

同步块的设置和锁的选择

这次作业的锁的设计和hw6没有特别大的差别,被多线程共享的对象:总等待队列,电梯的reset情况。我的调整是将AtomicBoolean改成了AtomicInteger,表示新增的doubleReset状态。

bug

注意A或B双轿厢等待另一电梯离开换乘层时,被唤醒的条件,应该是电梯处于下行(A,若B则是上行),且到达换乘楼层下(或上)的一层。若仅仅是到达换乘楼层上(或下)的层,会出现等待已经被路过换乘楼层前一层的电梯A或B唤醒,导致相撞。

心得体会

  • 线程安全:非必要条件下不要写锁的嵌套
  • 层次化设计:本单元我采用了生产者-消费者模型,生产者为输入线程,消费者为各个电梯线程。生产者到消费者之间的托盘为调度器线程。这个模型只需在共享对象上加锁即可满足线程安全问题。
  • 总结:多线程变幻难测,需要综合考虑各个线程的运行,需要程序员的细致编写和充分测试来保证线程安全和功能正确。
...全文
65 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

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

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