OO第二单元博客总结

唐逸凡-24231209 2026-04-24 13:42:14

架构设计

类关系图

img

线程类

类名功能
InputThread输入线程,读取输入并存入全局请求队列
DispatchThread调度线程,将请求分配给电梯
ElevatorThread电梯线程,负责电梯运行控制

队列类

类名功能
RequestQueue全局队列
WaitingQueue电梯的等待队列,存放被receive但是没有上电梯的请求
ProcessingQueue电梯的处理队列,存放在电梯内的请求

策略类

类名功能
ElevatorOperator电梯运行控制策略
ElevatorState电梯状态类,同时也处理一些关于状态的计算
Shaft井道类,在双轿厢问题中作为第二层调度器,处理两个轿厢之间的状态设置、楼层竞争等

包装对象

类名功能
WrappedRequest对普通类进行包装,处理一些请求内部独立的方法

枚举类

类名功能
Direction包含三种方向
Type电梯类型,主轿厢记为main,副轿厢为sub

迭代

第一次作业

第一次作业包含的类为Main,InputThread,DispatchThread,ElevatorThread,RequestQueue,WaitingQueue,ProcessingQueueDirection

设计思路
第一次作业规定了请求对应的电梯,所以通过的关键点只在于线程同步线程能正常结束请求分配正确以及输出正确,而且这几个点,除了线程相关问题对于刚接触多线程的我们来说比较困难,其他的都非常简单。
通过之后,需要考虑的就是优化,这也是第一次作业的主要区分点。我采取的是捎带策略,即只携带同方向的请求。同时也要设计一下开关门策略,避免不必要的开关门时间浪费。这是我能想到的不中途踢人的情况下的最优策略,但是最后还是没进A房,其他佬卷性能真是卷疯了。

bug修复
没有bug

第二次作业

第二次迭代,没有安排请求对应的电梯,这就要求我们对实现调度算法。同时还加入了维修请求,这对调度和运行控制都提出了新要求
设计思路
在第一次作业中DispatchThread只是一个空壳,到了第二次作业,就需要实质性地担任调度责任了。
在第二次作业中,我新增了两个类ElevatorState,ElevatorOperator
ElevatorOperator是电梯的运行控制类,所以按理来说,在合理的设计架构下,第一次作业就应该存在这个类了,只是第一次为了方便,所有策略都放到电梯线程了。但是第二次作业处理维修请求,需要在策略类中新增很多控制分支,长度大大增加,继续放在电梯线程内部会导致电梯线程过大。
ElevatorState类是我进行调度的信息的来源,内部属性基本和电梯线程属性相同,实现getter方法以及一些内部独立的计算方法,后续阐述优化策略时会细说。
总体也是遵循了LOOK架构

维修策略
当接收到维修信号之后,电梯直接向F1运行,中途如果下人,开门下人,但是全程不上人。到了F1,清空电梯,完成的请求正常输出,没有完成的请求,将起点设置成F1,生成新请求发还给RequestQueue队列实例。然后开始维修。我的维修运行没有使用常规的电梯运行,而是在维修方法里写了一个固定的运动控制。结束之后,将维修控制信号还原,回归正常的电梯运行策略。

优化思路
我采用的是计算电梯惩罚分来进行电梯分配。在DispatchThread中,针对不同的电梯情况,会被赋予不同的惩罚分

  1. 考虑请求的起始位置和电梯当前楼层,每相差1层,score加1

  2. ElevatorState中,根据当前状态和目标楼层,可以预测到达目标楼层的方向。根据捎带策略,如果不同向,score加20

  3. ElevatorState中,根据当前状态和目标楼层,可以预测到达目标楼层的整体重量(不考虑载重限制),最后score加上基础分predictWeight * 0.2,如果predictWeight超过400kg,再加上额外惩罚分40分。

  4. 至于处于维修状态的电梯,直接返回maxScore = 1000

最后,选取惩罚分最小的电梯,把这个请求分配给对应电梯。但是由于我的默认电梯是0号电梯,所以,当所有电梯都维修的时候,把这个请求回退给全局队列。接着分配下一个。

bug修复

  1. 第一个bug,是由于踢人时机的错误,我的电梯在维修还没有结束的时候就有receive,改了一些控制信号就解决了

  2. 另外一个是比较普遍的bug,就是当5部电梯同时维修的时候,请求就会全部分配给同一个电梯,导致超时。针对这个bug,我在取请求的时候新增了一个计数器统计当前有效电梯个数cnt,然后由维护了一个全局请求数globalCnt,从而生成一个控制信号valid。当valid无效的时候,会卡在取请求这一步,一旦valid有效,就开始进行分配。valid有效就保证了少电梯的情况下,电梯不会接收过多请求。

  3. 在修第二个bug的时候,又引入了新的bug。当由于valid无效导致阻塞时,后续所有的请求也会被阻塞,包括维修请求,这就会导致维修时间超出7秒限制,遍历队列,直接返回特殊请求就可以解决

第三次迭代

第三次作业新增了双轿厢的更新和回收请求,也就是说需要考虑双轿厢的控制算法。
设计思路
新增了Shaft井道类,用来统一管理同一个井道中的两个轿厢,同时在每一个调度相关的类中假如isDouble信号,用来标识是否处于双轿厢状态

优化思路
依然采用第二次的优化思路,只是把算分的方法放入Shaft中。先假设这个请求如果进入这个井道,会被分配到哪一个轿厢,然后计算这个轿厢的分。如果请求需要跨换乘层,那么惩罚分再加20

bug修复
valid信号中加了一个信号,导致又出现了第二次作业中请求被一个电梯接收的情况。

线程问题

多线程中,解决线程问题的方法有很多,java内置了很多类是线程安全的,直接使用这些应该不太需要考虑线程问题。当然了,线程处理,最基础的还是sychnorize关键字和wait(),notifyAll()方法,如果不细究的话,我认为每一次修改共享对象的时候,需要对这个修改的目标线程使用notifyAll。但是如果细扣线程操作导致的性能问题,那感觉太花时间了。

AI使用

这一单元的作业使用的是Gemini 3.1 flash。它在给出思路、理解题目和查找错误等方面还是比较可以的,很多线程同步导致的问题都是它帮我解决的。跟它讨论比起跟ds讨论也舒服很多,我给它提出我自己的实现思路,它会根据我的思路完善给出具体的或者更好的实现路径和实现方式,不会反复向我推荐它自己的思路。但是,我基本没有使用过它写的代码,大部分都是纯手写的。照搬的只有一处,是RequestQueue中的take方法,我自己实在不知道怎么写,直接复制了它的代码。

另外就是让AI给我生成测试用例,把互测要求和输入要求给它,然后再加上一些描述与限制,它生成的测试用例其实也还是不错的,比自己手写快多了。

有了大模型,这些编程题其实难度并没有那么大。我现在使用的AI,直接把指导书扔进去让它直接生成一个完整的的代码,可能不一定正确,但是基本也大差不差了。但是,Gemini 3.1其实也算是比较老的模型了,现在最新的GPT,Codex这些,一次性把题目做对感觉也不是没可能。就我认识的人里面,有好几个都是vibe coding领域大神,AI用得出神入化,最后成绩也还不错,有人还能次次进互测。

课程建议

其实觉得第一次作业有点过于简单,反而让后面两次作业的任务量有点大。
感觉可以把维修请求放到第一次作业,这样只会在捎带和踢人策略中做一些额外的处理,没有那么简单,但是其实也说不上难。把维修加到第一次作业,第二次就专门处理调度分配,感觉这样稍微好一点。

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

309

社区成员

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

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