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


如图,本次作业我只使用了synchronized锁,主要是对共享数据类型ReqTable类中的方法进行保护,例如对EndSign的读和写操作需要加锁。
同时,在写操作方法结束时需要加上notifyAll()方法,用于唤醒正在wait()该锁的所有线程。
由于本次作业是分配给指定的电梯,所以实际上可以没有调度器,但为了后续作业以及可扩展性的要求,本次作业我仍然写了调度器Controller类,其中决定分配给哪个电梯的方法的内容就是简单的按照输入来分配。
InputThread类及其实例化对象、ReqTable实例化的allReqTable对象、Controller类及其实例化对象,这三者构成生产者、共享资源、消费者;Controller类及其实例化对象、ReqTable实例化的paraTable对象、ElevThread类及其实例化对象(即每一个电梯),这三者构成生产者、共享资源、消费者;除此之外,关于电梯运行策略,我选用的是LOOK算法:
if (needOpen()) {
return ElevOp.OPEN;
} else if (!isEmpty()) {
return ElevOp.MOVE;
} else if (paraTable.isEmpty() && paraTable.isEnd()) {
return ElevOp.OVER;
} else if (hasPsgFwd()) {
return ElevOp.MOVE;
} else if (hasPsgBwd()) {
return ElevOp.TURN;
} else {
return ElevOp.WAIT;
}



如图,本次并没有增加许多同步块与锁(锁仍然沿用synchronized),由于reset的增加,使得在电梯线程中增加了对应的读写rstSign的方法,这些方法需要上锁。
本次需要自行进行调度,我在电梯中增加了一个计算“分数”的方法calcScore(),该方法可以根据当前电梯的状态(如速度,容量,载客数,等候数,是否处于重置状态,与目标楼层的距离等)来计算出一个该电梯的分数,这样当调度器想要将一个请求分配给电梯时,只需要计算出六个电梯各自的分数是多少,然后选择分数最高的一个电梯。
注意,若电梯处于重置状态时,也是可以进行分配的,否则可能出现将所有请求全部分给一个电梯,造成TLE的情况(五部电梯处于重置,只有一部电梯可用时),但是由于需要输出RECEIVE,并且只能在reset结束之后才能输出,因此这里可以在电梯内部再增加一个缓冲队列,当reset结束之后再将缓冲队列中的请求放入请求等候队列中,并输出RECEIVE。



如图,本次也并没有增加太多的同步块与锁(锁还是用的synchronized,我曾一度以为CTLE的原因是没用读写锁的原因,然而并不是这样,还好没改),本次作业的线程安全问题大爆发,是bug出现最多的一次作业,与双轿电梯的出现有关,因为原先输入线程只需要读取输入完毕就可以让调度器停止,但现在由于双轿电梯重置时,内部的乘客以及等候的请求必须重新分配,再次经由调度器进行分配,因此调度器的停止条件发生了较大改变。
本次的调度策略与上次几乎一样,不同之处在于增加了一些限制,如不能把双轿电梯无法接到的请求分配给他,这在电梯中计算得分的时候就可以做到。
我防止碰撞的策略非常特别,我将换乘楼层固定地分配给A电梯或是B电梯,依据是先前分配之后记录的“联通队列”。例如若将换乘楼层6楼分配给了A电梯,则该电梯的不连通的地方就是6楼和7楼之间,则分配完之后将这一不连通的地方记录下来;在下次分配时,若仍然是在6楼换乘,就根据先前记录的联通队列,将换乘楼层6楼分配给B电梯,这样可以保证所有电梯的集合是联通的、均匀的。
该方法的好处是可以避免电梯碰撞时的线程安全问题,坏处是性能可能有一定下降。
我在第七次作业的bug是最多的,因此重点说一下第七次作业中的bug
先输出IN再输出RECEIVE
这个问题是由于我在将请求加入到电梯的等候队列中的时候,先加进去再输出RECEIVE造成的,这样会导致加入请求队列之后,电梯立马就作出反应,人进入了电梯,然后是一瞬间后才输出了RECEIVE。
解决方法是先输出RECEIVE,再将请求加入等候队列。
调度器无法停止
这个问题是遇到次数最多的,前文也提到过是由于本次作业与前两次的结束条件不一样了,因此会导致各种死锁、死wait()等情况发生,一般都是不可复现bug。
解决办法是print大法。
将双轿电梯无法接到的请求分给了他
只需要更改算分方法即可解决。
reset时的各种属性的更改重置先后顺序导致输出逻辑有误,进而WrongAnswer
反复仔细思考reset时的步骤,充分测试。
学完多线程之后才体会到了线程安全的难度之大,以及它的重要性。很多时候遇到的线程安全问题都是不可复现的,因此需要严格遵守线程安全规范来写代码,调试的时候也应该在关键的地方进行输出相关信息(如果是使用print大法的话),除此之外,一个多线程程序即使测试了好几十遍都没问题,那也是可能存在bug的(比如强测中我遇到的WrongAnswer,也就是前文提到的第一个bug,只有在非常极限的一行代码的时间内才能复现),因此要多多考虑各种边界条件,以及对各种输出的逻辑的仔细思考。
有了前面的多次迭代经验,本次的层次化设计自我感觉良好,例如我将电梯线程和电梯分为两类,电梯类只是存一些电梯的参数;将请求列表单独封装出一类,实现各种定制化的加入、删除等操作;将策略提取为一类,每次电梯调用其中的方法来获取指令;将电梯的指令进行枚举……
第三次作业之前我都自认为自己的架构较为良好,但到了第三次作业,如此多的bug让我开始怀疑,但实际上这是线程安全的问题,如果不是前两次作业的良好架构的话我可能还会遇到更多非线程安全问题的bug。