BUAA-OO-第二单元总结

肖杰方-24371471 2026-04-25 11:27:12

一、同步块、锁

​ 对于绝大多数情况,我都是使用Synchronized关键字来上锁,之所以这样选择,是出于对安全的考虑。但是在F2楼层的防撞机制上,我选择使用显示锁来操作,因为我的Shaft本身存在其它数据,如果使用Shaft本身的锁来控制电梯的话,效率将会显著下降。

​ 在多线程中,锁的基本作用是实现互斥。在获取了 this 锁后,其它线程无法篡改内部数组状态。避免了check-then-act类的并发错误。除此之外,如waitnotifyAll这样的语句保证了线程的同步性,这在生产者-消费者模式下十分重要,可以避免轮询、忙等这些消耗CPU时间,降低CPU性能的行为。

​ 需要注意的是,我们应该尽量避免锁的嵌套使用,如果先A再B和先B再A同时出现,那么就很大概率出现死锁。

二、调度器

​ 在首次作业中,因为题目指定乘客乘坐的电梯编号,所以我的调度器仅仅是按照输入来判断目标的。后续两次作业中,调度方式可以自由选择,在这种情况下我选择了影子电梯作为调度方法。

​ 在使用影子电梯之前,我还使用过启发式的打分方法,根据目标对象与电梯当前楼层的距离、目标对象是否与电梯顺路等相关信息进行打分,随后加上载重相关的惩罚分数。但是问题在于,这种方法无法体现电梯内部乘客与外部等候队列的真实影响,比如开门关门对于时间的消耗。所以我最终换用了影子电梯模拟打分。

​ 影子电梯的关键在于获取各个电梯的快照,里面包含了电梯的所有信息,通过这个快照,我们就可以将影子电梯的打分与电梯本身的运行隔离开来,避免影响电梯本身的运行。在第二次作业中,我纠结于将“所有乘客下电梯的时间”还是“目标乘客下电梯的时间”当作打分依据,前者强调总时间少,而后者追求平均时间少。根据普通的推理我难以判断二者熟优熟劣,只能通过评测机来判断——在大量的测试之后,我倾向于选择前者,也就是计算总时间。

​ 第三次作业引入了双轿电梯,这个时候计算总时间就难以胜任打分任务了,因为双娇电梯不能保证目标乘客在目标楼层下电梯——很有可能是中途被甩下来的。于是我选择使用平均速率来打分,也就是(time_target/distance),其中time_taeget代表目标乘客下电梯时间。

​ 在后续的本地测试中,我发现我的电梯会在运行时接受上一层的乘客,这对于性能是毁灭性的打击,因此我在获取快照时保留了电梯的运动状态isMoving

三、程序bug

​ 在第一二次作业中,我在本地和公测中均未发现bug。

​ 但是在第三次作业中,我的程序出现了较为严重的问题。本地测试时我发现在某种输入下,我的电梯线程将永久沉睡,无法正常结束。这是因为副电梯在回收完成之后没有及时唤醒正在沉睡的主电梯,而isEnd的唤醒在电梯回收之前。就这样,主电梯一直在等待副电梯回收完成的信号,可惜它永远不会来。

​ 修复的方式其实很简单,在副电梯彻底结束之前notifyAll就行了。

​ 除此之外,还存在一个比较危险的点——我的特殊请求处理和行为判断之间存在极为短暂的窗口,如果调度器在这个窗口内分发了特殊请求,我的Strategy就会抛出“无法判断指令需求”的异常。为了修复这个问题,我选择将异常抛出语句修改为CONTINUE信号,让电梯重新处理一次特殊请求,这样就很好的规避了这个问题。

​ 值得注意的是,这个问题在本次测试中极难触发,这与多线程的处理顺序紧密相关,而这是难以复现的。之所以能发现这个问题,是因为我频繁地让AI检查代码。(感谢AI大人)

四、线程安全

​ 为了保证线程安全,除了上述提到的死锁风险之外,就是由“决策”和“行为”分离带来的窗口问题,这一点不仅仅在调度线程中出现过,在电梯wait执行的过程中也十分明显,为了避免相关问题,我们必须在真正执行wait指令之前再次判断isEmpty,只有这样才不会忽略窗口期加入的请求。

五、层次化设计

​ 在全局视角下,我的代码层次分明:Dispatcher线程负责全局的收发与统筹,作为所有请求的处理中心,它会根据每个电梯仓的情况进行责任划分,规划着他们的行为。Shaft化身部分经理,它负责具体的任务安排与向上的情况反馈,最高层无需了解电梯的具体情况,由中间层将信息封装,降低了耦合性。最后是当工人Elevator,它作为请求的最终执行者,只负责执行相关的命令。

​ 在电梯内部也存在三大平行的层次——“动作执行层”,Elevator线程类;“策略判断层”,Strategy类;“数据管理类”,ElevatorTable类。他们三者相互合作,共同构成了电梯这一个巨大的整体。这样做不仅降低了各个类的耦合性,还同时避免了其它线程对于Elevator的访问,更不用在Elevator里面加synchronized了。

​ 但是这样的做法是有代价的,因为会产生“类爆炸”问题,这些庞杂的类让每次迭代都十分煎熬。

img

六、大模型

​ 本次作业我使用了三种大模型工具

​ 首先是gemini,我通常用它来确认思路,在每次作业开始前与其讨论,了解完成作业需要用到的知识点。在教学方法,gemini有独特的优势,它可以很好的讲解多线程的实现原理与线程安全的保障方式,但是在具体实现的时候,它往往会遗漏某个很多细节。

​ 其次是gpt,尽管它的语言表述能力十分有限,但是它有全面的视野,可以找出代码中存在的各种风险问题,所以我使用它来检查。此外,我还是用它来修改我的评测机。

​ 最后是copilot,因为它属于Agent,我一般使用它来降低代码中的重复度,它可以提炼出代码中重复出现的片段,并且封装成一个新的方法。

七、课程建议

​ 1、添加要求,在电梯运行前输出Move,这样可以防止量子电梯这样的作弊行为。

​ 2、允许提交特殊的hack数据点。因为多线程的结果难以复现,同学们提交的hack数据不一定能hack到别人。可以通过提交文档说明来解释自己的数据点,经过助教审核来判断对错。最后安排统一的测试(比如一个数据点反复测试100次)

​ 3、允许电梯自动结束receive,而不是非得在下电梯和临时调度开始之后,这样可以让电梯调度更加灵活。

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

305

社区成员

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

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