BUAA OO 第二单元 电梯

本文介绍了作者在模拟电梯系统项目中的学习过程,包括使用生产者-消费者模式实现电梯调度,遇到的并发问题如ConcurrentModificationException,并讨论了简单的随机调度策略和电梯内部的LOOK策略。此外,还提到了在维护电梯功能上的挑战,如电梯杀人事件的bug修复,以及对锁和同步机制更深入的理解。
摘要由CSDN通过智能技术生成

前言

第二单元的主要任务是模拟电梯,主要涉及调度、电梯内部运行策略、换乘等,培养多线程编程的能力。

第一次作业

第一次作业的要求较为简单,模拟6部电梯的运作以及调度即可。

分析

笔者参考了上机所给的代码架构(即输入流、调度器、电梯三种线程),采用生产者-消费者模式。
通过输入流(生产者)读入乘客请求,将其加入到总的等待队列(waitQueue)中,由调度器(托盘)对waitQueue进行调度操作,分配到每个电梯(消费者)的乘客请求队列(elevatorQueue)里,由每部电梯执行乘客的运送操作。
调度方面,笔者尝试通过乘客的fromFloor、toFloor、电梯的当前层数、电梯的方向,设计一下调度从而提高性能,但因为感觉太过复杂而放弃,使用random调度:对每个乘客请求取一个1-6之间的随机数,取到多少就是几号电梯去接乘客。
电梯内部,我参考了学长的博客:采用LOOK策略,建立枚举类Advice,通过getAdvice方法分析电梯下一步的动作(move,openAndClose,reverse,over等)。
(笔者傻了,没仔细看官方的jar包,自己设计了Request类,效果和官方给的PersonRequest类的功能相同)

加锁方面,我对RequestQueue类里所有方法进行加锁,因为涉及多个线程对waitQueue(总的等待队列)的读写。此外,对电梯类里涉及elevatorQueue的方法,笔者也选择加锁,方法为synchronized(elevatorQueue)。

调度器设计

笔者尝试写一下调度,即根据当前请求的fromFloor、toFloor和电梯当前的floor、电梯的运动方向,将人放到合适的电梯里,但因太过复杂直接放弃)
笔者最终采用random调度:对每一个请求,在0-5之间random一个整数,取到多少就是elevators.get(num)(电梯数组)这部电梯去跑。性能方面还算可以,主要是笔者自身的代码出现一些问题导致性能分不够高。而且相对自由竞争,耗电量低。

电梯内部设计

笔者参考了学长的博客,采用LOOK策略。
在这里插入图片描述

根据得到的策略,让电梯执行相对应的任务:

    public void run() {
        while (true) {
            Advice advice = getAdvice();
            if (advice == Advice.OVER) {
                break;                              //电梯线程结束
            } else if (advice == Advice.MOVE) {
                move();                             //电梯沿着原方向移动一层
            } else if (advice == Advice.REVERSE) {
                direction = !direction;             //电梯转向
            } else if (advice == Advice.WAIT) {
                elevatorQueue.waitRequest();        //电梯等待
            } else if (advice == Advice.OPEN) {
                openAndClose();                     //电梯开门
            }
        }
    }

    public Advice getAdvice() {
        if (canOpenForOut() || canOpenForIn()) {
            return Advice.OPEN;
        }
        //如果电梯里有人
        if (curCapacity != 0) {
            return Advice.MOVE;
        }
        //如果电梯里没有人
        else {
            //如果请求队列中没有人
            if (elevatorQueue.isEmpty()) {
                if (elevatorQueue.isEnd()) {
                    return Advice.OVER; //如果输入结束,电梯线程结束
                } else {
                    return Advice.WAIT; //如果输入未结束,电梯线程等待
                }
            }
            //如果请求队列中有人
            if (hasReqInOriginDirection()) {
                return Advice.MOVE; //如果有请求发出地在电梯“前方”,则前当前方向移动一层
            } else {
                return Advice.REVERSE; //否则,电梯转向(仅状态改变,电梯不移动)
            }
        }
    }

UML类图

在这里插入图片描述

bug分析

中测时出现问题:电梯上人时要将请求从电梯的请求队列中删除,于是我直接边遍历边删除:

    for (Request request : elevatorQueue) {
        if (request.getFromFloor==this.floor) {
            elevatorQueue.remove(request);
        }
    }

然后交上去直接报错,显示异常:ConcurrentModificationException,发生并发修改。
后来在助教的帮助下,修改了下写法:先用一个temp数组将请求队列里要上去的人存起来,然后遍历temp数组,每次遍历时删除elevatorQueue数组里的
强测出现一个bug,canOpenForIn方法中没有对elevatorQueue对象加锁,导致冲突报错,电梯没法关门。 不稳定复现
互测时程序被房友拿去跑评测姬,同样存在不稳定复现的情况(random大法),但是房友手下留情没有hack我orz
此外还有一个小问题:
(举例)电梯停在1楼,人从7楼下到6楼,笔者的电梯会从7楼上到11楼然后再下到6楼。没有处理好掉头的时机,导致性能分有损失。
而且这个小bug在笔者写作业的时候出现:笔者最开始没设电梯的顶,所以面对这样的请求,电梯会一飞冲天太空电梯
笔者选择在move方法里修改:如果电梯到达11层且方向向上,则电梯掉头向下;电梯到达1层且方向向下,则电梯掉头向上。懒人修改法其实可以写个方法判断一下

第一次作业总结

第一次作业是我初次接触多线程编程,开始写的时候一头雾水,直到周五上机才提供了一些思路,对加锁、同步等有了一点理解:出现多个线程对同一个对象进行操作时,一定要谨慎判断是否需要加锁。

第二次作业

第二次作业新增了两个功能:

  • 增加电梯并自定义初始楼层、速度、容量。
  • 维护(删除)电梯,要求电梯在两层楼以内停下,将电梯内人在该层放出去,电梯进行维护,不再接人。

分析

对于以上两种功能分别分析。

  • 增加电梯:比较简单,直接将新电梯加入elevators(电梯数组)里,使其可以参与调度,并start即可。在输入流中进行上述处理。
  • 维护:本次作业难点。维护电梯要求更为复杂一些,要先将电梯从elevators中移除,使其不再接受调度,然后将电梯里的人放出去,同时也要将调度到该部电梯的等待队列里的人放出去,放回到waitQueue(总的调度队列)中,最后开始维护。
    维护时,需要判断电梯内部人的情况:如果电梯内部乘客的toFloor等于当前楼层,则乘客正常出电梯;否则将乘客请求变为从当前楼层到乘客的目的楼层,加到throwPersonRequest中。
    笔者在电梯类里增加了Arraylist<PersonRequest> throwPersonRequest元素,将电梯里的人和等待队列里的人放在这个数组中,在维护时对该方法直接get即可得到踢人队列。(理论上不应该存在什么问题但是笔者出了bug
    在输入流中新增移除电梯、得到返回请求队列的操作。

调度器修改

由于电梯数量可变,所以对随机数范围进行修改,改为0-elevators.size()。其余未变动。

UML类图

在这里插入图片描述

bug分析

重头戏来了:电梯杀人事件
这个bug其实在笔者写的时候就出现了:电梯维护之后没有返回请求队列。
中测之前笔者进行了一下修改,在接收到维护请求后,延迟500ms,等电梯踢完人之后再get返回队列。然后强测t了两个点(
强测过后的修改bug里笔者把延迟时间修改为400ms,调整了电梯踢人和开关门的时间顺序(先open再踢人sleep(400)再关门),RTLE的问题解决了。
然后又尝试对stderr里面报错的方法加锁,最终得到一个奇怪的bug:当电梯刚好运动到这一层,且刚好接到维护请求时,电梯有几率不会理请求队列里的乘客,开始踢人、维护,踢人之后没有其他电梯再去接请求队列里的乘客,即没有返回正确的请求队列。
这个bug不是很容易复现,但是强测的点确实阴间以至于笔者交了十几次才把所有点都跑对,现在也没弄明白到底要怎么改才能对(投机取巧式改bug不可取

第二次作业总结

第二次作业里电梯维护让我吃了不少苦头,也让我对锁的用法有了一些更深的理解。整体架构仍然沿用第一次作业,并未进行大幅度修改,将自己写的Request换成官方给的PersonRequest。
由于电脑环境原因,笔者在第五次作业里没能使用官方给的投喂包进行评测,把评测姬当成评测工具,所幸bug不多便于修改。
第六次作业里笔者重新配置了下环境,至少能用官方的投喂包进行评测,在改bug时通过手搓数据、分析数据点,也修改了一部分的bug。
多线程修复bug和单线程相比难度提升不少,由于执行顺序的不确定性,一个bug可能会跑好多次才可能跑出来。在下单元里应该尝试使用评测姬。

第三次作业

第三次作业笔者摆了(屑笔者),但是也有一点点点点思考
第三次作业新增功能:

  • 控制每个楼层处于服务中的电梯数量和只接人的电梯数量
  • 新增的电梯有无法到达的楼层,当初始六部电梯全部维护之后会出现换乘需求

分析&&调度器设计(猜测)

笔者认为本次作业需要修改的部分主要为调度器。
对于每一个乘客请求,先遍历电梯队列里是否有fromFloor和toFloor都可达的电梯并记录这样的电梯的数量(num),然后在0-num之间取random分配请求。
如果没有可直达的电梯,则需要利用图进行路径规划实现换乘,找到路径后将这个请求拆分为几个可达的请求,然后按顺序按电梯执行,将人送到相应位置。
对于新增的第一个功能,笔者考虑新增一个“信号量”作为所有电梯的共享资源,当电梯开门时增加、电梯关门时减少,以此进行控制。

总结

第二单元里笔者干了很多第一单元里没干过的事(互测进AB房,还有摆了没进入互测(?)),总体来说不是很满意,第三次作业应该可以尝试一下而不是放弃。
经历了第一单元的各种摧残之后,第二单元里或许会相对从容一点,面对bug时也没有之前那样慌张。初次接触多线程,笔者对锁、同步块、wait&notify的理解、应用从一团糟到逐渐熟悉,在网上搜了不少资料、也问了很多同学,经历很痛苦也很有趣。
第一次作业经历了好几次重构,上机之前自己写了一点然后感觉不对推翻了,然后看学长的博客无脑模仿又推翻了,又看了另外一名学长的博客又推翻了,最后看了第一个学长的博客,自己理解、自己总结,一步一步走,完成设计。自己写的东西才是自己的,完全走别人的老路终究不好走。
业经历了好几次重构,上机之前自己写了一点然后感觉不对推翻了,然后看学长的博客无脑模仿又推翻了,又看了另外一名学长的博客又推翻了,最后看了第一个学长的博客,自己理解、自己总结,一步一步走,完成设计。自己写的东西才是自己的,完全走别人的老路终究不好走。
电梯月结束了,听说第三第四单元的oo强度就不会这么高了,希望自己能有所突破,认真对待每一次作业,多做评测,不留遗憾。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值