304
社区成员
发帖
与我相关
我的任务
分享在不同阶段,针对不同的共享资源选择了不同的锁对象:
同步块的设置遵循了最小化锁持有时间的原则:
Queue)中,直接使用 synchronized 修饰方法,确保原子性的读写。openAndClose)中,仅在涉及乘客上车、从队列中删除请求的关键时刻才进入同步块,而在 Thread.sleep 模拟耗时时主动释放锁。没有写太过复杂的调度器(如计算时间、影子电梯等等),只做了一个简单的评分,为每台电梯计算一个分值,分值越低表示该电梯承接该任务的代价越小:
基础分:基于 abs(personFloor - elevatorFloor) * 5,即物理距离越近,分值越低。
方向加成:如果电梯运行方向与乘客目标一致且未经过乘客,则视为顺风车,分值最低;如果方向相反,则大幅增加惩罚分(+1000),因为这通常意味着乘客需要等待电梯折返。
空闲激励:空闲电梯(direction == 0)会获得较小的基础惩罚分(+100),鼓励系统利用闲置资源。
虚拟重量:计算分值时,我不只看电梯内的实时重量,还通过 VirtualWeight() 加上了已经分配给该电梯但尚未上车的乘客重量。
超重保护:如果“实时重量 + 虚拟重量 > 400”,该电梯分值直接飙升(+10000)。这避免了多名乘客蜂拥至同一台电梯导致频繁由于超重而开门失败,从而减少了不必要的电量损耗(开门最耗电)和时间浪费。
楼层可达:在第三次作业中,增加了对楼层可达性的强约束判断,通过 getMinFloor() 和 getMaxFloor() 过滤掉无法到达乘客起始层的电梯。
采用了分级交互,调度器作为中间层,通过两个关键队列与不同线程协作:
waitQueue.takeRequest() 阻塞式地获取任务chooseElevator 算法选定目标电梯,并调用其私有队列的 addRequest 方法。在第三次作业中,交互变得更加紧密。电梯在完成维修(finishMaint)或升级(update)后,会主动回调调度器的方法,更新调度器的状态表(status[])。在三次作业中, 第二次被发现的bug最多。
isAllFinished 函数。该函数不仅检查 WaitQueue 是否为空,还必须扫描所有电梯的内部状态(轿厢内是否有乘客、私有队列是否为空、是否处于非 NORMAL 状态)。主线程必须在所有电梯真正进入静默后才能退出,确保了生命周期的完整性。chooseElevator 时,缺乏对电梯当前状态的过滤。在 chooseElevator 中增加状态校验。当电梯状态非 NORMAL 时直接跳过。同时,在 DispatchThread 的主循环中增加二次等待:如果 chooseElevator 返回 0(无可用电梯),调度器应进入 wait状态,直到有电梯完成维修或升级并发出 notifyAll 信号。主要还是通过分析输出的日志dbug,析日志中 RECEIVE、IN、OUT、ARRIVE 的出现时间点,重构每个请求的生命周期,从而定位是在哪个环节出现了逻辑断裂(例如:人还没出电梯,电梯就开始维修了)。在关键节点(获取锁、释放锁、进入 wait、发送通知)打印带有时间戳和线程 ID 的日志。
在作业中,电梯的状态(如维修、升级、正常)和它持有的资源(乘客、私有队列)必须保持原子性。当一个电梯进入维修状态(REP_ACCEPT)时,它清空队列并将乘客重新投入总等候队列。如果这组操作不是原子的,调度器可能在电梯还没清空队列时又给它分配了新乘客,导致请求丢失。在三次作业中,通过将状态与队列操作封装在同一个锁作用域内,确保了电梯在转换角色的瞬间,其资源状态对外界是逻辑自洽的。
线程安全不仅是防止“数据竞争”,更要处理过早唤醒和信号丢失,wait() 必须放在 while 循环中。例如调度器在没发现可用电梯时进入 wait,唤醒它的可能是某部电梯修好了,也可能是输入结束了。通过严密的条件检查,确保线程被唤醒后必须重新评估全局状态,避免了在不具备执行条件下盲目运行导致的非法分配。
在本次多线程的三次作业中,对于多线程的debug问题,ai还是比较擅长的,虽然ai很难找出某一条信息错误的点,但是能很好的找出线程之间存在的交互问题,指出潜在的竞态和死锁,提供排查思路。
终于度过了电梯月,纵观三次作业,感觉第一次作业还是比较简单的,毕竟指定了电梯,第二次作业最难,尤其是加上了维修状态,当时爆出了好多bug,熬到很晚才de完。。在多线程的世界里,仅仅是前后语句的顺序的改变就可能产生不同的结果和状态。