BUAA-OO-二单元阶段总结

李首墨-22371327 学生 2024-04-19 16:45:18

同步块的设置和锁的选择

  • 锁与同步块中处理语句之间的关系

首先,要详细解释一下同步块(synchronized)的详细内容:

synchronized修饰的内容在运行前需要取得锁,而锁的作用是施加在一个对象上的:

synchronized底层使用monitor来控制锁的活动,具体内容在此不再赘述。

同步块内的处理语句可以安全执行,防止多个线程同时尝试修改同一资源时可能发生的冲突或数据不一致问题。

常规写法有:

synchronized (queue) {
}

public synchronized void setEnd(boolean isEnd) {
        this.isEnd = isEnd;
        notifyAll();
}

施加在方法上的相当于对this对象进行同步

主要由三种:

修饰实例方法,对当前实例进行加锁,进入同步代码前需获得当前实例的锁

修饰静态方法,对类对象加锁,要先获得当前类对象的锁

修饰代码块,指定加锁对象,对给定对象加锁,要先获得给定对象的锁

确保修饰过程的原子性,程序进行顺序执行

  • 同步块的设置和锁的选择

三次作业的架构大体相同,都是由 输入线程、调度线程、电梯线程 组成

其中,输入与调度间通过 waitQueue 来储存接收到的请求,调度线程从中取出请求并分配,调度与每个电梯间通过 processingQueue来存储与取出分配给电梯的请求。

在首次作业中,仅涉及简单的电梯送人问题,因此线程间的数据交换是相对简单的,仅靠一个共享类(RequestQueue)就可以,该类有两类实例,一种是作为waitQueue另一种作为processingQueue,而我们的同步操作也仅需负责规范该共享类的数据,在读写方法上加上synchronized进行修饰,使其不能被两个线程同时读写。

当进入后两次作业时,事情发生一些变化,无论是Reset操作还是电梯共享层都会导致电梯内的人返回waitQueue因此我们也需要将 waitQueue 传入电梯线程,这里需要对waitQueue设计一个能够加入多个人的操作,尽管之前设计的加入方法是同步修饰的,由于for循环遍历是非原子性操作,为了防止读写不一致也要加上synchronized修饰,除此之外,在设计电梯共享层时我新增了一个TransferFloor类来作为双轿厢电梯间的共享类。

在算法优化中也涉及到同步问题,在这部分,我们需要从调度器中获得电梯线程的具体信息,于是我还为这几个可能被取用的信息设计共享方法来保持一致性。

作业中的调度器设计

在调度器的算法设计上我采用了就简的写法,设计了一个电梯预期时间的标准(存在于电梯线程内的计算方法),通过在调度器进行调度时依次调用方法计算比较不同电梯的优先级来选择出分配电梯。

在算法中,主要考虑电梯时间上的送人更优,对电梯电量消耗并没有过多考虑

预期时间计算规则:

    public synchronized int getDistance(int from, int to) {
        if (judge(from, to)) {
            return Integer.MAX_VALUE;
        }
        int waitNum = processingQueue.getWaitNum();
        if (direction) {
            if (from >= curFloor || curNum == 0) {
                return (Math.abs(from - curFloor) + waitNum * (maxFloor - minFloor)) * speed;
            } else {
                return ((2 * maxFloor - from - curFloor) + waitNum * (maxFloor - minFloor)) * speed;
            }
        } else {
            if (from <= curFloor || curNum == 0) {
                return (Math.abs(curFloor - from) + waitNum * (maxFloor - minFloor)) * speed;
            } else {
                return ((from + curFloor - 2 * minFloor) + waitNum * (maxFloor - minFloor)) * speed;
            }
        }

    }

对于同向请求,预期时间为电梯中途不停留的接人时间+等待电梯的人的最坏情况(距离最远)

对于异向请求,预期时间为电梯到达最远在返回中途不停留的接人时间+等待电梯的人的最坏情况(距离最远)

这其中考虑到了电梯与请求的相对距离,电梯运行速度与等待该电梯的人数,较好的比较了电梯接人的时间

结合线程协同的架构模式

结合教程与往年学长博客作为参考,选用的 (生产者-消费者) 的架构

下面解释一下各文件的作用

img

InputThread 接受输入数据,加入总等待队列

MainClass 程序启动与初始化

Process 电梯线程

RequestQueue 共享容器

Schedule 调度器

Strategy 决定电梯行为的方法类

TransferFloor 共享层占位 (仅第三次作业)

第一次作业:

img

第二次作业:

img

第三次作业:

img

  • 识别出三次作业稳定的内容和易变的内容,并加以分析

三次作业中整体的架构并没有大变化,只是在不断丰富电梯的行为(Reset/双轿厢)

如何实现双轿厢的两个轿厢不碰撞的

多设计一个共享类(在A/B两电梯间)

public class TransferFloor {
    private boolean atTransferFloor;

    public TransferFloor(boolean atTransferFloor) {
        this.atTransferFloor = atTransferFloor;
    }

    public boolean ifAtTransferFloor() {
        return atTransferFloor;
    }

    public void setTransferFloor(boolean at) {
        atTransferFloor = at;
    }
}

在电梯进行Move操作前后进行判断,进入共享层TransferFloor 置 True 出共享层 置 False

程序出现过的bug及debug方法

程序出现的Bug主要分为三种;

  1. 格式问题:对Receive的出现位置理解错误,在Reset期间还可以Receive
  2. 逻辑问题:例如楼层变化晚于输出,在输出后可能发现条件的变化问题,导致输出错位
  3. 线程不安全:同步块使用方法不恰当导致同时读写丢失数据/死锁
  4. 性能问题(调度算法不完备):互测中出现的同时进入多人会同时分派到同一电梯导致RTLE

最基本的DeBug方法 + 尽量本地复现 + 输出状态进行分析

因为对于多线程分析断点设置可能并不方便/使用输出大法会好一点

  • 格式Bug是最简单的,只要仔细阅读说明文档就好(害羞羞)

  • 对于逻辑问题,结合状态输出找到具体是哪一环发生了异常(状态更新不及时,判断逻辑有漏洞等等)

  • 性能问题(调度算法不完备) 补全算法机制(主要是互测的问题),如 电梯Reset后应当还可以接受请求但是要延时输出,调度判断需要考虑已分配人数

  • 线程不安全:

本次作业DeBug最大的敌人:

难复现,难修改

主要还是要对自己程序的同步块设置与架构有较好的理解,在单元开始时,对同步部分了解不够深入,写的同步块常出现冗余不正确的情况,但是随着认识的加深就可以进行逐步修改

DeBug时结合状态输出使用瞪眼法和流程分析法,静下心来最好。

真不行就祈祷评测时候不发生罢

心得体会

一山更比一山高,本单元相比前一单元只能说是有过之而无不及,开始对多线程没有什么深入的理解在第二次作业吃了好多苦,还好明白后,三单元平稳落地,走过一趟也不觉得那么苦了,希望后面两个单元诸事顺遂。

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

301

社区成员

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

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