OO第二单元作业总结

高子贺-22373138 学生 2024-04-19 16:57:31

前言

本单元的任务是实现满足需求的多电梯调度模型,主要考察多线程的运用。在本单元作业的实现过程中,本人也是遇到了很多的问题,最终也或好或坏地进行了解决,下面就对我的作业的整体架构、设计特点以及在完成作业中遇到的问题进行分析与总结。最终hw7迭代后的UML图如下:

img


hw7-src.png

可以看到在结构中Elecator类有着巨量的属性与方法,我认为这可以辩证地看待:一方面这保证了对于电梯各种操作的整体性,让架构总体较为清晰;但是另一方面这对于电梯的维护和增减功能带来了不小的难度。Strategy类负责捎带策略的实现,我使用的是LOOK策略,经过测试此算法相对于ALS策略有较高一些的性能。Controller类负责电梯的增添与删减,也控制着各电梯线程的启动与停止。RequestPool类负责存放所有乘客请求,所有电梯线程在运行时共享此请求池,进行请求的拿取与处理。前文说到RequestPool只存放所有的乘客请求,那么对于重置请求,我的实现是直接在读入请求时识别出请求的类别,如果是重置请求则直接通过Controller对电梯进行重置操作,期间保证一系列重置要求。RequestWithId类是在hw6及以后加入的类,目的是为了适应调度的算法,在给每个请求分配电梯后给请求加上电梯编号的属性,从而保持和hw5乘客请求的属性一致。
hw7的时序图如下:

img


hw7_Sequence.png

hw5

作业要求

hw5要求实现多部电梯运行模型,满足能处理已指定电梯的乘客请求

具体实现

此次作业要求相对简单,关键在于整体架构的设计(万事开头难)。我则仿照课上实验的类似架构,实现对于电梯的控制(如前言中所述)。关于电梯的运行的实现如下:

@Override
public void run() {
    do {
        recent = handlePassengerMovements(recent);
        strategy.searchRequest(this);
        direction = strategy.nextDirection();

        if (shouldCloseDoor()) {
            long time = closeTheDoor(recent);
            if (time != -1) {
                recent = time;
            }
            recent = tryMoveElevator(recent);
        }

        if (Math.abs(direction) == 2) {
            requestPool.getElevator().put(this, true);
        } else {
            requestPool.getElevator().put(this, false);
        }

    } while (!(shouldTerminate()));

}

recent指的是最后一次操作(如开门、关门等)的时间,设置recent可以帮助控制电梯任何操作的时长。这种控制电梯运行时长的策略唯一比较困难的就是保证电梯的一切操作都在recent的监视之下,否则就会因为电梯行动过快或者过慢导致电梯的运行错误。

遇到的问题

作业中遇到的最主要的问题就是电梯运行策略的实现。如果电梯已经在运行中,那么捎带策略的实现是相对容易的,主要的困难在于如何处理电梯静止的情况以及电梯为空的情况。在电梯静止时我选择让电梯等待属于它的请求的进入,一旦有请求进入就会让电梯设法运行。当然判断是否存在对应电梯请求的方法在Strateg类中实现:

public void searchRequest(Elevator elevator) {
    this.passenger = elevator.getPassenger();
    this.floor = elevator.getFloor();
    this.direction = elevator.getDirection();

    HashSet<RequestWithId> allRequest = requestPool.getAllRequest(id, passenger);

    updateDirectionBasedOnRequests();

    needTurn(allRequest);

    updatePickUpAndPutDownLists(allRequest);
}

只有当对应电梯的请求存在时,才会根据策略改变电梯的一系列属性,否则让电梯停在原地并保持原来的属性。这也恰好满足了后续hw6和hw7中关于RECIEIVE的要求,只有有请求的时候电梯才会动。电梯为空的解决办法与静止类似。

hw6

作业要求

本次作业在hw5的基础上添加了电梯的重置操作,并且每个乘客请求不指定电梯,需要自己设计电梯调度策略。

具体实现

关于重置操作,我从重置请求的输入开始,就将这类操作与一般的乘客请求进行分离。这样的处理可以让重置操作保证较高的优先级,不用处理重置过慢的情况:

while (true) {
    // ...
    Request request = elevatorInput.nextRequest();
    if (request == null) {
        break;
    } else {
        if (request.getClass() == PersonRequest.class) {
            // do something
        }
        else { // reset request
            // do something
        }
    }
    // ...
}

这里的重置处理将通过Controller类直接操控对应电梯,体现重置操作被看作更高权限的电梯控制。重置的具体内容我的做法是先将电梯内乘客放到电梯外,乘客的请求变为新的请求加入请求池;然后直接改变电梯的属性,等待一定时间后开始运行。这样处理可以将重置操作简单化,也比较贴近现实生活中的场景。
关于电梯的调度,我首先运用轮换分配,检查了作业除了调度之外所有操作的正确性;然后使用影子电梯实现性能的改善(为了确保测试的正确性,最终交到网站的版本仍使用轮换分配)。

遇到的问题

由于所有做的关于电梯操作的时间控制是用recent属性来控制,所以添加操作时对于recent的处理需要格外的小心。在添加操作时对于recent的监控不当使我在TimableOutput的错误问题中挣扎了很久。

hw7

作业要求

第七次作业要求额外实现双轿电梯的运行,相应地也增添了第二类重置请求——将原来的单轿厢电梯变为双轿厢电梯,并设置换乘楼层。

具体实现

对于新增的第二类重置请求,我选择将原来的单轿厢电梯停止,在Controller中新增两个电梯模拟双轿厢,并且把原来单轿厢已接收到的请求按照出发楼层重新生成对应双轿电梯的请求放入请求池。这对于电梯的性能会有一定程度上的损失,但是可以较大程度上优化电梯调度的难度:

public void resetDoubleElevator(int id, long time, int capacity, int transferFloor) {
    Object lockObj = new Object();
    elevators.get(id).resetPreDoubleElevator(transferFloor);
    addElevator(id * 10 + 1, time, capacity, 1, transferFloor,
            transferFloor - 1, lockObj, transferFloor);
    addElevator(id * 10 + 2, time, capacity, transferFloor, 11,
            transferFloor + 1, lockObj, transferFloor);
}

同时,为了避免双轿电梯在换乘楼层的相撞,对两个电梯共同设置一把锁,在电梯即将进入换乘楼层时,对于电梯的操作进行上锁,同时电梯在换乘楼层进行完相应操作后手动往回移动一层,防止出现电梯停在换乘楼层不同导致双轿电梯失效的问题:

public void run() {
    do {
        // ...
        if (isDouble && ((floor + direction) == transferFloor)) {
            synchronized (lockObj) {
             // do something at transferfloor and leave
            }
        else {
            // normal run
        }
        // ...
    } while (!(shouldTerminate()));
}

遇到的问题

令我意想不到的是在hw7中我的代码出现了线程提前结束的问题!追踪原因发现是在处理双轿电梯的请求时,需要等到到达换乘楼层并放下乘客后才会对伙伴电梯生成新的请求加入请求池,这太晚了,如果请求池在这之前变成空的了,就会自动判定请求都处理完毕,从而关闭线程。对此我的解决办法是:在电梯在换乘楼层刚刚打开门并且关闭门之前就生成新请求并加入请求池,在判断线程是否应该结束时加上判断门是否是关闭状态。

public boolean shouldTerminate() {
    if (!requestPool.getPersonRequests().isEmpty()) {
        return false;
    }

    for (Elevator elevator: elevators.values()) {
        if (!elevator.getPassenger().isEmpty()) {
            return false;
        }
        if (elevator.isOpen()) {
            return false;
        }
    }
    return true;
}

心得体会

在作业的层次上,我选择通过实现一个总控制器——Controller来在上帝视角控制电梯的start()与end(),其余跟电梯相关度高的一切操作都交由电梯本身处理。对于不同类别的请求,我选择将重置请求的优先级提升至Controller的地位,从而保证重置的及时性,然后乘客请求设置为与Elevator的地位相同。关于线程安全,我在处理请求池更新请求时进行了锁操作,以及在双轿厢电梯防撞机制上也添加了锁操作,保证线程的安全。各个电梯之间是没有通信的,这也保证不会出现因为线程间通信而出现的一系列错误。
个人感觉第二单元的整体难度相比于第一单元有所提升,具体的体现是在作业上花费的时间变多了,而且思考问题的精力消耗也变多了。这是我第一次接触多线程问题,也算是多掌握了一个技能。经过这一单元的学习我发现面向对象并非我想象的那么固化,而是范围很广、知识很多的一个学科。面向对象对我的思维带来了很大的提升,在日常生活中处处我都能感觉到面向对象的存在。
通过这三次作业我也反思出来一个道理:车到山前必有路,我的代码在每一次迭代前都显得破烂不堪,没有任何简单的迭代思路(当然这也与我在hw5中对于整体架构设计没有把握有关),但是经过不断的思考与尝试,总能在原来的基础上成功实现新的需求。这对于我的信心的增长是十分有帮助的。

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

301

社区成员

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

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