309
社区成员
发帖
与我相关
我的任务
分享
线程类
| 类名 | 功能 |
|---|---|
| InputThread | 输入线程,读取输入并存入全局请求队列 |
| DispatchThread | 调度线程,将请求分配给电梯 |
| ElevatorThread | 电梯线程,负责电梯运行控制 |
队列类
| 类名 | 功能 |
|---|---|
| RequestQueue | 全局队列 |
| WaitingQueue | 电梯的等待队列,存放被receive但是没有上电梯的请求 |
| ProcessingQueue | 电梯的处理队列,存放在电梯内的请求 |
策略类
| 类名 | 功能 |
|---|---|
| ElevatorOperator | 电梯运行控制策略 |
| ElevatorState | 电梯状态类,同时也处理一些关于状态的计算 |
| Shaft | 井道类,在双轿厢问题中作为第二层调度器,处理两个轿厢之间的状态设置、楼层竞争等 |
包装对象
| 类名 | 功能 |
|---|---|
| WrappedRequest | 对普通类进行包装,处理一些请求内部独立的方法 |
枚举类
| 类名 | 功能 |
|---|---|
| Direction | 包含三种方向 |
| Type | 电梯类型,主轿厢记为main,副轿厢为sub |
第一次作业包含的类为Main,InputThread,DispatchThread,ElevatorThread,RequestQueue,WaitingQueue,ProcessingQueue,Direction
设计思路
第一次作业规定了请求对应的电梯,所以通过的关键点只在于线程同步,线程能正常结束,请求分配正确以及输出正确,而且这几个点,除了线程相关问题对于刚接触多线程的我们来说比较困难,其他的都非常简单。
通过之后,需要考虑的就是优化,这也是第一次作业的主要区分点。我采取的是捎带策略,即只携带同方向的请求。同时也要设计一下开关门策略,避免不必要的开关门时间浪费。这是我能想到的不中途踢人的情况下的最优策略,但是最后还是没进A房,其他佬卷性能真是卷疯了。
bug修复
没有bug
第二次迭代,没有安排请求对应的电梯,这就要求我们对实现调度算法。同时还加入了维修请求,这对调度和运行控制都提出了新要求
设计思路
在第一次作业中DispatchThread只是一个空壳,到了第二次作业,就需要实质性地担任调度责任了。
在第二次作业中,我新增了两个类ElevatorState,ElevatorOperator。ElevatorOperator是电梯的运行控制类,所以按理来说,在合理的设计架构下,第一次作业就应该存在这个类了,只是第一次为了方便,所有策略都放到电梯线程了。但是第二次作业处理维修请求,需要在策略类中新增很多控制分支,长度大大增加,继续放在电梯线程内部会导致电梯线程过大。ElevatorState类是我进行调度的信息的来源,内部属性基本和电梯线程属性相同,实现getter方法以及一些内部独立的计算方法,后续阐述优化策略时会细说。
总体也是遵循了LOOK架构
维修策略
当接收到维修信号之后,电梯直接向F1运行,中途如果下人,开门下人,但是全程不上人。到了F1,清空电梯,完成的请求正常输出,没有完成的请求,将起点设置成F1,生成新请求发还给RequestQueue队列实例。然后开始维修。我的维修运行没有使用常规的电梯运行,而是在维修方法里写了一个固定的运动控制。结束之后,将维修控制信号还原,回归正常的电梯运行策略。
优化思路
我采用的是计算电梯惩罚分来进行电梯分配。在DispatchThread中,针对不同的电梯情况,会被赋予不同的惩罚分
考虑请求的起始位置和电梯当前楼层,每相差1层,score加1
在ElevatorState中,根据当前状态和目标楼层,可以预测到达目标楼层的方向。根据捎带策略,如果不同向,score加20
在ElevatorState中,根据当前状态和目标楼层,可以预测到达目标楼层的整体重量(不考虑载重限制),最后score加上基础分predictWeight * 0.2,如果predictWeight超过400kg,再加上额外惩罚分40分。
至于处于维修状态的电梯,直接返回maxScore = 1000
最后,选取惩罚分最小的电梯,把这个请求分配给对应电梯。但是由于我的默认电梯是0号电梯,所以,当所有电梯都维修的时候,把这个请求回退给全局队列。接着分配下一个。
bug修复
第一个bug,是由于踢人时机的错误,我的电梯在维修还没有结束的时候就有receive,改了一些控制信号就解决了
另外一个是比较普遍的bug,就是当5部电梯同时维修的时候,请求就会全部分配给同一个电梯,导致超时。针对这个bug,我在取请求的时候新增了一个计数器统计当前有效电梯个数cnt,然后由维护了一个全局请求数globalCnt,从而生成一个控制信号valid。当valid无效的时候,会卡在取请求这一步,一旦valid有效,就开始进行分配。valid有效就保证了少电梯的情况下,电梯不会接收过多请求。
在修第二个bug的时候,又引入了新的bug。当由于valid无效导致阻塞时,后续所有的请求也会被阻塞,包括维修请求,这就会导致维修时间超出7秒限制,遍历队列,直接返回特殊请求就可以解决
第三次作业新增了双轿厢的更新和回收请求,也就是说需要考虑双轿厢的控制算法。
设计思路
新增了Shaft井道类,用来统一管理同一个井道中的两个轿厢,同时在每一个调度相关的类中假如isDouble信号,用来标识是否处于双轿厢状态
优化思路
依然采用第二次的优化思路,只是把算分的方法放入Shaft中。先假设这个请求如果进入这个井道,会被分配到哪一个轿厢,然后计算这个轿厢的分。如果请求需要跨换乘层,那么惩罚分再加20
bug修复
valid信号中加了一个信号,导致又出现了第二次作业中请求被一个电梯接收的情况。
多线程中,解决线程问题的方法有很多,java内置了很多类是线程安全的,直接使用这些应该不太需要考虑线程问题。当然了,线程处理,最基础的还是sychnorize关键字和wait(),notifyAll()方法,如果不细究的话,我认为每一次修改共享对象的时候,需要对这个修改的目标线程使用notifyAll。但是如果细扣线程操作导致的性能问题,那感觉太花时间了。
这一单元的作业使用的是Gemini 3.1 flash。它在给出思路、理解题目和查找错误等方面还是比较可以的,很多线程同步导致的问题都是它帮我解决的。跟它讨论比起跟ds讨论也舒服很多,我给它提出我自己的实现思路,它会根据我的思路完善给出具体的或者更好的实现路径和实现方式,不会反复向我推荐它自己的思路。但是,我基本没有使用过它写的代码,大部分都是纯手写的。照搬的只有一处,是RequestQueue中的take方法,我自己实在不知道怎么写,直接复制了它的代码。
另外就是让AI给我生成测试用例,把互测要求和输入要求给它,然后再加上一些描述与限制,它生成的测试用例其实也还是不错的,比自己手写快多了。
有了大模型,这些编程题其实难度并没有那么大。我现在使用的AI,直接把指导书扔进去让它直接生成一个完整的的代码,可能不一定正确,但是基本也大差不差了。但是,Gemini 3.1其实也算是比较老的模型了,现在最新的GPT,Codex这些,一次性把题目做对感觉也不是没可能。就我认识的人里面,有好几个都是vibe coding领域大神,AI用得出神入化,最后成绩也还不错,有人还能次次进互测。
其实觉得第一次作业有点过于简单,反而让后面两次作业的任务量有点大。
感觉可以把维修请求放到第一次作业,这样只会在捎带和踢人策略中做一些额外的处理,没有那么简单,但是其实也说不上难。把维修加到第一次作业,第二次就专门处理调度分配,感觉这样稍微好一点。