OO-24-Unit2

王畅-22373217 2024-04-18 15:46:54

OO-24-Unit2


一、同步块和锁的设置

    这次作业中对于同步块和锁的设置遵从一个原则:即只在涉及共享对象的读写的部分加锁。如果涉及容器,则在容器所属的类上加锁,通过在类上锁的方法对容器修改而不获取容器本身。

    比如:

private synchronized void allocate() {
    for (int j = 0; j < inputList.size(); ++j) {
        Request request = inputList.get(j);
        ......
    }
    inputList.clear();
}
public synchronized void add(Request request) {
    inputList.add(request);
}

    然而这其中也有很多同步块设置混乱的问题。有些同步块设置在了该设的方法上,而有些方法是靠在同步块中执行来实现同步,对后续扩展产生了很大的混乱。


二、调度器设计与调度策略

img

图1省略部分内部方法。
    架构如图,其中SegmentTree类为第二次作业加入,左侧两个Double相关类为第三次作业加入。虽然出于工期不足以及思路混乱等原因,实际架构要更为混乱,为了便于解释架构对实际类图进行了拆分。

    从第一次作业就确定基本的调度策略是电梯会在通道内往返扫描,仅在到达当前路线上最远的目标后掉头,对所有顺路的请求都进行捎带。电梯类本身只存储与运行及请求相关的信息,决策完全由Guider类进行。Guider类会维护一个树状数组来快速查找前方区间是否还有上/下车请求并以此指定策略,同时决定当前楼层是否需要停留。由于请求指定了电梯序号,Controller (即调度器) 只需要负责分发请求即可。

    第二次作业调度器才真正拥有了调度的职能。由于耗电量不会导致无法通过强侧,而效率队强侧和性能分都有影响,自然是优先选择时间上不会出问题的调度策略。考虑到只要电梯能保证尽可能多捎带乘客不出现满载,以及越近的电梯到达时间更快的朴素思想,选择让电梯维护一个线段树来维护区间内最高的实载人数就可以解决这个问题。在这个思想下,调度器在尽可能保证分配出去的请求所在电梯的实载人数不会超过上限的前提下,分配给移动到请求出发楼层所需时间最短的电梯即可。对于电梯,只需要添加reset相关功能即可,基本没有逻辑上的改变。

    第三次作业引入双轿厢电梯实际上对于实现并没有太大的逻辑改变——毕竟换乘楼层是固定的,不需要考虑更复杂的运动控制问题。这里本着能复用就不多改的想法,采取了将原有的电梯线程直接变成一个次级调度器,连接原调度器和新开的两台电梯的思路实现。次级调度器类和双轿厢电梯均继承自电梯类。次级调度器重写run方法和add方法,负责将从controller接收到的请求分派给AB两台电梯,并从两台电梯处获得并整个调度用的信息传给controller使用,这样controller就完全不必区分管理的电梯是普通电梯还是双轿厢电梯。而双轿厢电梯本身与普通电梯也几乎没有区别,只需要新增离开换乘楼层以及判断是否相撞的逻辑即可。此处采用了最简单的方法:在controller中设了一个数组来表征每个通道中的换乘楼层是否被占用,如被占用就等候即可。

    纵观整个单元,在不强求性能的前提下理论架构在事后整理还是比较清晰的。然而因为工期和懒狗等问题导致整个实际架构变得非常臃肿,最后连基本的预想都没有实现,也只能从自己身上找原因了。


三、bug分析与debug方法

    首先就是导致第三次作业强测爆炸,但却及其简单的bug。在Guider的构造函数中,出现了这样一段代码:

this.peopleMax = 6;

    这个很显然的错误在前两次作业都是正确的,因为电梯在构造的时候初始容量一定是6。而第三次作业在构造双轿厢电梯时的指令实际是reset,可以指定reset后的两台电梯的容量,而我过于相信前次作业留下的接口——即Elevator的构造函数。

public Elevator(String index, int floor, int min, int max, int capacity,
                    ElevatorController controller, int speed)

    由于Elevator的构造函数的确包含了容量初值的传入,但Guider却没包含,导致了构造初始值只传入了一半的尴尬情形,最终导致了电梯满人,但决策者认为你该停下接人的离谱场景。

    至于debug方法,其实多线程程序debug最大的问题除了本身不好复现以外,就是在不能单步调试的情况下不好定位bug位置了。对于复现问题,我也没有什么太好的办法,只能多测;但定位bug位置其实在看到了bug数据的输入输出时其实也能猜出大概,之后找到bug可能出现的位置输出一些相关数值,观察是否符合运行逻辑就能比较有效地找出bug了。


四、心得体会

  • 1、 尽可能在类内部解决线程同步的问题,而不是在类的使用时再通过同步块进行控制。在有一定开发周期的项目中,与其相信之后的自己能够清晰地记得之前的逻辑,还不如早点控制好以便之后直接使用。
  • 2、永远厘清架构在下手,永远不要忘记自己可以继承类而不是在原类上添油加醋。

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

301

社区成员

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

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