309
社区成员
发帖
与我相关
我的任务
分享synchronized内置锁在两个共享变量类请求队列RequestQueue和电梯井ShaftContext中对所有关键方法使用synchronized修饰,保证这些共享数据写和读的安全性。
AtomicIntegerRequestQueue中使用原子类变量assignedLoad来统计电梯负载数(即接受的乘客数),在接受请求时增加,乘客离开电梯时减少,用来让调度器判断分配给负载最少的电梯以及控制每个电梯请求的量,是个仅计数的轻量操作,用原子变量较高效。
总的来说,所有涉及共享资源读写的处理语句都被包裹在对应锁的同步范围内,大部分都是最简单的锁:方法上加synchronized修饰,保证代码的正确性,对并发效率应该有一定损失。
DispatchThread从InputThread生产的队列mainQueue中拿取请求,按策略分配给某个ElevatorThread对应的队列elevatorQueue,再让该电梯进程从对应队列取请求。
分配给当前负载数最少且满足双轿厢楼层要求的电梯,负载数由原子变量assignedLoad统计,如果所有能载客的电梯负载数达到6就让调度器休息,判断是否楼层要求如下:
private boolean canServe(int carId, int shaftId, int from, int to) {
int state = f2Locks.get(shaftId).getState();
if (state == ShaftContext.STATE_UPDATING) {
return false;
}
if (carId <= 6) {
if (state == ShaftContext.STATE_NORMAL) {
return true;
} else if (state == ShaftContext.STATE_DOUBLE ||
state == ShaftContext.STATE_RECYCLING) {
return (from > 2 || (from == 2 && to > 2));
}
} else {
if (state == ShaftContext.STATE_DOUBLE) {
return (from < 2 || (from == 2 && to < 2));
}
}
return false;
}
在代码中添加输出相关数据的代码,通过分析输出判断问题,而且注意在输出中显示是哪个进程的,不然完全分不清。
想要保证线程安全,首先是保护好共享资源,读共享变量中所有读写的方法添加synchronized修饰,让数据在同一时间只有一个对象读或者写;然后是一些操作的原子性,比如判断和操作要一起完成,防止中间被其他进程插入出问题;接着是每个进程修改共享对象后要用notifyAll()让其他线程看到;最后是多个锁要注意锁的顺序保持一致,防止出现死锁的情况。
层次化设计主要是依照生产者消费者模型,InputThread与DispatchThread用mainQueue联系,DispatchThread与ElevatorThread用elevatorQueue联系,两个对应的ElevatorThread之间用ShaftContext管理双轿厢的情况,总体架构非常清晰,将职责分离开来,易于维护。
我主要使用Gemini作为辅助工具。我将整体架构与一些属性方法设计好,部分实现细节会询问大模型,以及帮助我debug以及判断锁相关的问题。总的来说大模型在多进程方面的表现还是比较差的,不太能发现一些锁或进程的问题,需要我去手动调试以及增加输出内容,但可以让它帮我梳理出每个进程的输出,便于我检查。
多进程的并发开发确实具有很大的考验,尤其是随机性太大了,有些bug甚至很难复现,可能我的代码现在还有某处存在没有把两个操作合成原子操作的问题,只是目前没被样例测出来。现在学习的锁还是比较粗的,性能较差一点,后续应该还需要更深入的学习,毕竟性能差距太大了,这在第二次作业给了我很深的印象,我本来以为不可能出现超时的问题的,没想到把请求全分给一个电梯后直接就超时了,和并行的差距巨大,现在的全添加synchronized修饰应该更接近串行一点,还是有很大优化空间的。