BUAA_OO_Unit2

冉啟何-22373028 学生 2024-04-20 18:47:44

BUAA_OO_Unit2

一、同步块与锁

第五次作业

第五次作业中,临界资源只有各个电梯的请求列表 RequestTable 类,因此对其的读写操作都在 synchronized 块中。

同时,为了防止电梯线程轮询,当电梯等待请求输入时,会访问该类中的 waitForQueue() 方法,该方法同样由 synchronized 修饰:

if (isEmpty() && !end) {
    if (isEmpty() && !end) {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    notifyAll();
}

第六次作业

第六次作业中,临界资源有总请求列表 WaitList 类、单个电梯的请求列表 PersonTable 类,对这两类的读写操作都在 synchronized 中。

同时还加入了 RequestCounter 类,用于辅助结束线程,该类由电梯线程和输入线程访问,其中的速写均要在同步块中实现:

public synchronized void acquire() {
    // ...
}

public synchronized void complete() {
   // ...
}

第七次作业

第七次作业新增 ElevatorMap 类,用于记录12个电梯进程的状态,判断该进程是否起用。对这类的读写操作都在 synchronized 中。调度线程对其进行读操作,电梯线程对其进行写操作。

还加入了 TransFloor 类,用于记录交换楼层是否被占据,由两个电梯进程进行读写。对其的读写操作同样需要在 synchronized 中实现。

二、调度器设计

第五次作业

第五次作业中,不存在多电梯协同调度。直接由输入线程将请求加入各个电梯的请求列表。

requestTableHashMap.get(request.getElevatorId()).addRequest(request);

UML图 如下:

img

调度策略上仅存在单步电梯的调度策略,这里使用了 Look 算法。我们日常的电梯也大多使用这种算法。

Look算法:

  • 初始化电梯的运行方向。
  • 判断是否需要开门:
    • 有人目的地为当前楼层,开门让他出;
    • 有人在该层等待,且目的地在电梯前方,开门让他进。
  • 判断电梯是否有人:
    • 有人:移动至下一层;
    • 无人:
      • 等待请求队列为空且输入线程结束,电梯线程结束;
      • 等待请求队列为空且输入线程未结束,电梯等待;
      • 请求队列不为空,且请求发出地在前方,电梯移动至下一层;
      • 请求等待队列不为空,且出发地在后方,电梯转向。

第六次作业

第六次作业不再指定电梯,且新增 RESET 请求,并要求输出 RECEIVE

UML类图

img

这次作业相对于第五次而言难上许多,首先是不在指定电梯,这就要我们建立一个新的调度器将请求分配给各个电梯;然后是添加了 RESET 请求,这个请求相当于添加了换乘请求;最后是添加的 RECEIVE 请求,将调度算法中最简单的自由竞争禁掉了。

在这次调度策略中,我选择了随机数调度,这种调度算法较为简单,容易实现,且上线限较高。缺点就是下限太低了。

    private int whichToReceive(Person p) {
        Random random = new Random();
        ArrayList<Integer> elevatorIds = new ArrayList<>();
        getIds(elevatorIds, p);
        while (elevatorIds.isEmpty()) {
            try {
                Thread.sleep(1200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            getIds(elevatorIds, p);
        }
        return elevatorIds.get(random.nextInt(elevatorIds.size()));
    }

getIds() 方法判断那些电梯能够接受请求,如正在 RESET 的电梯不能 RECEIVE

第七次作业

第七次作业调度算法与第六次一样采用了随机调度法。因此构架类似。

img

private int whichToReceive(Person p) {
    Random random = new Random();
    ArrayList<Integer> elevatorIds = new ArrayList<>();
    getIds(elevatorIds, p);
    while (elevatorIds.size() <= 1) {
        try {
            Thread.sleep(1200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        getIds(elevatorIds, p);
    }
    return elevatorIds.get(random.nextInt(elevatorIds.size()));
}

由于第六次的问题,这里在五个电梯重制时,再等1.2秒,以防大量请求被分配给一部电梯,导致超时。

三、如何使双轿厢不碰撞

这里在双轿厢的电梯井中,两部电梯线程有一个共享类 TransFloor

    public synchronized void setExFlag(ExFlag exFlag) {
        if (exFlag == ExFlag.BUSY) {
            waitForFree();
        }
        this.exFlag = exFlag;
        notify();
    }

    private synchronized void waitForFree() {
        notify();
        while (exFlag == ExFlag.BUSY) {
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

电梯移动时检测一下,下一步是否是到交换层。

        if (elevator.isDoubleCar() && curFloor == elevator.getExFloor()) {
            exFlag.setExFlag(ExFloor.ExFlag.BUSY);
        }
        TimableOutput.println("ARRIVE-" + curFloor + "-" + elevator.getString());
        if (elevator.isDoubleCar() && curFloor - direction == elevator.getExFloor()) {
            exFlag.setExFlag(ExFloor.ExFlag.FREE);
        }

四、出现的 bug 和 debug方法

出现过的 bug

  1. 轮询:电梯在等待时,电梯线程轮询,占据 CPU 时间;

    处理方式:在电梯等待时,使用 RequestTable 类中的 waitForQueue() 方法。

  2. 死锁:由于线程不安全导致各个线程进入 waiting 状态,无法进一步进行;

    处理方式:

    • 打印中间量,找到bug所在;
    • 运用工具,查看程序运行情况;
    • 分析代码逻辑,发现线程不安全的地方。
  3. RESET 状态的电梯调度做的不好,导致超时。

    这一部分主要是没处理好对于调度的处理,在五个电梯重制时,再等1.2秒,以防大量请求被分配给一部电梯。

面对多线程程序的debug方法

我这里使用了Java自带的一个工具 Visual VM,他可以监控各个线程的运行状态。

线程状态

img

CPU时间

img

五、总结

三次架构中,第一次到第二次的架构变化更大些,而第二次到第三次的变化较小。

而第一次迭代主要是加入了调度器线程。

三次作业中的稳定内容是电梯类的各种属性。

易变内容包括输入线程中的结束方式,调度器的调度策略实现。

六、心得体会

  • 多使用工具。这次在查找如何查看CPU时间时,学会了使用 Visual VM 帮Debug解决了不少问题。
  • 多看讨论区。可以从中发现许多实用的想法。
  • 实现设计好架构,并做好层次化。由于这次没做好架构,写的时候反复重写了好几次,效率大大降低。
  • 线程安全对于多线程十分重要。线程不安全的后果是致命的,许多bug可能会因此难以复现。
...全文
40 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

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

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