301
社区成员
发帖
与我相关
我的任务
分享

第一次的做法直接main生万物了(x
在main函数创建了输入线程、调度器、电梯和请求队列,其中请求队列分为总请求队列(输入线程和调度器之间的托盘),和分请求队列(电梯各自持有的待处理请求队列)
InputThread读取输入放进总请求队列waitQueue,Dispatcher按照特定策略(这次是直接按照id)将总请求队列的请求分派给电梯的分请求队列,电梯处理自己请求队列里的请求
实现了两类请求队列,RequestQueue和RequestTable,其中的区别从名称也很容易看出来 x 前者是用ArrayList容器实现的队列,后者是为了方便按照楼层检索,用HashMap套HashSet实现的表
请求的分配用队列实现是因为符合先进先出的思想,而电梯请求的处理用HashMap实现是为了避免遍历,因为大多数时候都是按照楼层访问的
将请求队列ReqeustQueue和RequestTable的所有方法加上synchronized修饰,使得共享资源的访问不会发生冲突
按照id分发!


用于实现wait-notify机制
调度器和电梯在无需处理请求时均处于等待状态,需要在新请求加入时唤醒线程。对于总请求队列,设置Object phone;电梯设置Object elevatorPhone;重置请求设置 Object resetPhone作为共享资源,来限制特定语句块的访问
用于实现reset期间电梯功能的沉默
通过synchronized(resetPhone)使得”电梯reset“和”电梯请求分配“ 两个行为互斥(这个愚蠢的实现在后面出了大大的bug,且看后面bug分析)
由于学习多线程锁的使用和debug花了太多时间,只写了一个简单的条件优先分配策略
在和请求同向的、未满员的所有电梯里面,==优先选择最近的==,其次选择人少的;
如果不存在符合该条件的电梯,则直接选择人最少的
(这个策略在互测的时候被大家锤爆了ORZ)


isRunning用于标识电梯是否运行或存在isDoubleCarReset,区分两类reset请求;新增属性transferFloor,若为第一类reset指令缺省值为-1OS大法好,信号量救我狗命
由于在这种架构设计中,并不存在井道,因此12部电梯其实是相互独立的,用于识别两个电梯是否在同一的井道的标志只有id,如果id<6,该电梯的”另一半“就是id+6的电梯
那么这两部电梯的同步就是一个问题,主要涉及两点:
当电梯被reset为双轿厢电梯时,原电梯reset,伙伴电梯start,如何保证他们俩一起开始跑(也就是一起出reset)
在这里用了OS里面学到的信号量实现barrier机制:
电梯和伙伴电梯共享两个信号量sem1和sem2,初始值都为0,在原电梯reset的结尾加上
sem1.release();
try {
sem2.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
伙伴电梯reset的结尾加上
sem2.release();
try {
sem1.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
两部电梯就会一起离开reset方法啦
在上次的优先分配策略上进行了改动(加入了随机性),防止把所有请求塞给一个电梯导致的超时(被hack怕了),大概如下
if (satisfiedIds.size() != 0) { // 有满足需求(达到一定优先级别)的电梯
blabla……
} else { // 没有满足需求的电梯,在可到达的电梯中选择一个
return elevatorIds.get((int)(Math.random() * (elevatorIds.size() - 1)) + 1);
}
然后是针对双轿厢电梯的部分:
如何实现:双轿厢电梯只会被分配特定楼层范围内的请求?
写一个Elevator.inFloorRange(PersonRequest request)函数,加入分配的条件判断
到达换乘层之后干什么:
为了达成reset时不能receive的目的,简单的给调度器Dispatcher中为该电梯添加receive的代码上了锁;也就是说,如果电梯在reset过程中被分配了请求,调度器会一直等待直到该电梯reset完毕
设计太愚蠢了()这样会导致调度器直接被==硬控1.2s==,如果有新的reset请求进来就没办法及时处理了
果然临界区和锁的使用是一门大学问啊
解决方法:新增共享对象线程安全处理 isReseting,并加入buffer机制,reset过程中给电梯加入的请求会被加入buffer,等到结束统一输出receive
由于buffer机制,电梯在reset过程中可以接受请求,放在buffer里面,等到reset结束再输出receive;但是由于双轿厢电梯reset时会更改maxFloor和minFloor,存在这样一种情况:reset开始了但是尚未修改参数,此时调度器获取了reset前电梯的数据并将请求分配给该电梯,但是这个请求的楼层reset后的电梯无法到达。
可能的解决方式:1. 如果是第二类reset,在reset成功后再次将buffer中的请求丢回主请求队列重新分配,考虑到第二类reset最多出现六次,这种方法应该也不会造成太大的性能影响;2. reset成功后检查buffer中的请求,将无法完成的请求丢回主请求队列; 3. 第二类reset过程中电梯完全不接受请求
综合考虑后,在这里采取第二种方式进行修复
循环过程中修改了ArrayList,向buffer中加入了新请求,导致java.util.ArrayList$Itr.checkForComodification
解决方式:调整了出Reset的位置,避免在清空buffer的时候向buffer中新增请求
如果能考虑周全一次成型就最好了(x 感觉经常缝缝补补又出新bug
由于感觉synchronized够用就偷懒没换读写锁QAQ,现在感觉如果hw6就换读写锁,会对线程安全的理解更加深刻一些吧
信号量救我狗命
处于可拓展性考虑,在hw5就写了相对清晰的架构(比如在hw5其实没什么用的Dispatcher),后面基本没有大的改动
多线程的bug真的好难de(爆哭
以及特别特别感谢一起讨论问题、无私帮助我的老师们(比心