北航OO第二单元作业

同步块的设置和锁的选择

多线程电梯调度,我主要使用synchronized关键字设置同步块,锁住有可能出现共享资源访问的方法。这种锁的设置较为简单粗暴。并没有采用相对精细的锁(readWriteLock等)的方法。
对于这三次作业:

第一次作业主要是两处产生冲突的地方,第一个是输入线程InputThread和调度器Schedule线程,一个生产输入数据,一个分配输入请求。第二个地方是,电梯处理请求和调度器分配请求队列之间的冲突。使用同步块主要是锁住这些操作。

第二次作业,新增了RESET和RECEIVE两个方面,对同步块的设置影响不大。

第三次作业,新增双轿厢电梯。双轿厢电梯涉及电梯之间的协作,这是新增的冲突产生的地方,按照产生冲突的规则给部分方法加上同步块。

调度器设计和调度策略

第一次作业不涉及调度器设计。第二次作业需要自己设计调度器,我最初的想法是在顺序分配(例如7层有5个乘客,3部电梯,那么第1个乘客分配给第1部电梯,第2个乘客分配给第2部电梯,第3个乘客分配给第3部电梯,第4个乘客分配给第1部电梯)的基础上,设计简单的优化策略,即优先把请求分配到电梯运行方向和自己方向相同的电梯上,但是我没有考虑到极端情况下所有请求都分配到一个电梯,如果考虑人数问题修改还要监督每个电梯分配到的人数,会使调度器和电梯之间产生关联。为了简化复杂度,最重要的是自己被这些线程交互搞的身心俱疲,我选择放弃上面小的优化,采用较为均衡的调度策略。

因此,调度器和电梯之间的交互变得简单,这两者通过一个等待电梯分配的队列TwoDimensionalQueue产生联系,调度器Schedule和InputThread通过waitQueue等待队列产生联系。

三次作业架构设计

HW5

第五次作业整体使用生产者消费者模型,InputThread生产请求,Schedule分配请求,Elevator消费请求。第五次作业的难点在于电梯的运行策略和为后两次作业奠定基础。我在第五次作业的设计上没有考虑到未来的扩展。

在这里插入图片描述
Main类里面,启动所有线程,并按照生产者消费者策略执行。
UML协作图

HW6

第六次作业新增REVEIVE和RESET请求。调度策略简单的情况下RECEIVE涉及Schedule内部逻辑和电梯RESET状态的判断,RESET的电梯重置请求设计电梯自身行为改变和新增状态。
在这里插入图片描述
此次作业线程间新增了关联,Schedule调度器需要判断电梯是否处于RESET状态和直接改变电梯状态,让它开始Reset。调度器会因为电梯Reset过多而wait(),所以电梯增加了Schedule属性,用来唤醒调度器。
在这里插入图片描述

HW7

第七次作业新增双轿厢电梯,因此在Elevator类的基础上,新建一个DoubleElevator实现双轿厢电梯,该类新增的重点是,区分A,B和等待楼层的进出。因为对于双轿厢电梯,我不会把已经分配给双轿厢电梯的请求重新分配给等待队列waitQueue。同时,我删去了HW6调度器的优化操作。
在这里插入图片描述

如下是它的协作图和HW6类似:
在这里插入图片描述

三次作业中稳定和易变的内容

稳定的内容是代表请求本身的类,和容纳请求的队列。
例如:

public class RequestReset {
    private final int elevatorId;
    private final int capacity;
    private final int speed;

    public RequestReset(int elevatorId, int capacity, double speed) {
        this.elevatorId = elevatorId;
        this.capacity = capacity;
        this.speed = (int) (speed * 1000);
    }

    public int getElevatorId() {
        return elevatorId;
    }

    public int getCapacity() {
        return capacity;
    }

    public int getSpeed() {
        return speed;
    }

    public String toString() {
        return String.format("RESET_ACCEPT-%d", this.elevatorId);
    }
}

容易改变的内容是Schedule, Elevator, InputThread 这些线程。原因是,每次任务有改变,输入数据就会发生改变,同时调度器也会因此改变。如果电梯类的策略和类型改变,电梯新增工作模式,电梯类就会改变。这三个类和任务紧密相连,是易变类。

实现双轿厢不碰撞

首先通过限制双轿厢电梯的运行范围,使它们两个只有在换乘楼层可能会发生碰撞,新增三个属性

private int toTransferFloor; // 获得进入换乘楼层权限
private int wantToTransfer; // 想要进入换乘楼层
private DoubleElevator otherElevator; // 轨道上的另一个电梯

在电梯移动前,先判断电梯是否想要进入换乘楼层,即wantTofloor是否置1,如果为1,则通过调用adjustTo()方法,以确定toTransferFloor的值,该方法并没有进入同步区的控制,因此需要考虑两个电梯同时访问的情况,判断条件较为复杂。结合 toTransferFloor 和 wantToFloor 这两个属性确定电梯是否可以进入换乘楼层。

if (this.wantToTransfer == 1 && this.toTransferFloor == 0) {
        setZeroWant();
        Wait();
} // 反之可以进入换乘楼层或正常移动


public void adjustTo() {
    int otherToTransfer = this.otherElevator.getToTransferFloor();
    int otherWantToTransfer = this.otherElevator.getWantToTransfer();
    if (otherToTransfer == 1) {
        this.toTransferFloor = 0;
    } else if (otherToTransfer == 0) {
        if (this.wantToTransfer == 1 && otherWantToTransfer == 1) {
            if (this.doubleType == -1) {
                this.toTransferFloor = 1;
                //TimableOutput.println(toTransferFloor + "-" + doubleType);
            } else {
                this.toTransferFloor = 0;
            }
        } else if (this.wantToTransfer == 1 && otherWantToTransfer == 0) {
            this.toTransferFloor = 1;
        }
    }
}

调用的主要方法没有使用同步块和锁限制此方法,是一个比较危险的方法。
总之,主要思想是设置进入楼层的权限,控制电梯进入换乘楼层。
为了简化之后的调度策略,电梯进入换层楼层后,它不会停在换乘楼层而是无需控制直接离开。在离开前,把toTransferFloor这个属性置0,释放权限。它在逻辑上是没有问题的。

程序bug和debug方法

第五次作业,强测错误是REAL_TIME_LIMIT_EXCEED。电梯的运行策略出现问题,电梯好像不能正常停止。第六次作业是我bug出现的密集点。首先是Wrong Answer,电梯Reset后电梯的人未能成功进入waitQueue内,这是线程之间冲突导致的。其次解决这个问题后,hw6我没有考虑到RESET命令高优先级的问题,waitQueue阻塞后,RESET请求不能及时分配,使电梯在REset_ACCEPT后运行太多楼层,解决方法是将RESET请求和正常请求分开储存,而且获得RESET请求后,调度器立刻被唤醒。调度器调度出错,把所有请求分配给一个电梯,等等。线程不能正常退出也不时出现。

多线程debug是一件困难的事情。调试功能受限:数据量少或者线程不能正常退出这两种情况是利用调试的主要条件。分析程序,在自己认为可能出现问题的地方插入printf语句,输出属性或者不相干的数据,验证错误或缩小错误范围。这是我多线程debug的主要方法。

心得体会

本单元作业的层次化设计相较于第一单元表现的不明显,主要的层次化设计是InputThread ->Schedule -> Elevator 的生产,消费模式。自己应该加强设计模式和层次化设计方面的学习。

在我设计实践过程中,好像没有出现死锁问题,毕竟我设计的比较保守,想尽量减少线程之间的联系。设计出现的问题主要是线程协作时出现的出乎意外的问题。多线程debug尤为折磨,这种折磨产生恐惧,因为恐惧,所以厌倦,畏缩。多线程设计主要是理清线程之间可能纠缠的地方,分析设计好这些地方大抵可以高枕无忧,理清这些地方,本单元困扰自己的应该是运行策略和调度策略如何设计。正视多线程,它不过是折磨自己的纠错过程,实际上还是考验自己的策略设计能力。小心翼翼,三思后行,减少变量是克服多线程的重要思想。

  • 24
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值