大闸蟹的OO第二单元总结
OO的第二单元是讲多线程的协作与控制,三次作业分别为FAFS电梯,ALS电梯和三部需要协作的电梯。三次作业由浅入深,让我们逐渐理解多线程的工作原理和运行状况。
第一次作业:
第一次作业是傻瓜电梯,也就是完全不需要考虑捎带策略,只需要简单的把每一个人都送到目的楼层就可以。与以往写过的程序不同的是,这次要采取多线程的 模式,输入和输出并不同时,输入是按照时间投放的,输出也要包含时间信息。这次作业主要是想教会我们生产者——消费者模型的使用。然而,在第一次写这次作业的时候,我把输入当成生产者,调度器当作托盘,却把每一条请求都当作了一个消费者,试图让每一条指令单独去跑(这或许就是我对多线程最初的理解,然而其实这次作业中的多线程是让我们将输出和输入两个线程并发执行),导致出现了很离谱的错误。并且,在试图保证每两条请求的运行不能够出现重叠,我还想在调度器中设置该条指令是否结束的标志位,这个方法看上去是可行的,但是我当时对于notifyALL,wait等还不是很理解,甚至想尝试在run()方法的最后加入notifyALL(),企图来唤醒正在等待的线程,发现根本无动于衷23333,最后我甚至想到了wait(100)再去查询的zz方法,虽然这样能够跑出来,但是这是一个浪费CPU的轮询,而且线程会频繁的被唤醒然后wait。最终,在我积极的与同学交流的过程中,终于慢慢理解了这道题的本意,将其改成了输入生产者,输出消费者,变成了比较正常的思路。但改完之后,我还是遇到了问题,那就是怎么样让他停下来。输入线程根据给的样例遇到null就会停,那输出怎么办。于是,我在输入线程遇到null停止之前把null也传进来,输出线程遇到null也停止,这样终于搞定了第一次作业。
类图如下:
度量分析如下:
可以看出,这次由于正确的采用了生产者消费者模式,所以各个类和方法的复杂度都比较低,设计的比较好。
第二次作业:
第二次作业是als电梯,也就是要采取捎带策略的电梯。指导书中给的捎带方法为将与主请求同一方向的请求作为捎带请求加入,并且不断地去更新主请求。我采用的方法是将第一条请求作为主请求,然后将与他同方向的,并且是当前运行到的楼层可以携带的请求全部加到电梯运行的请求队列中,然后将电梯请求队列中的请求都跑完并且电梯中没有人作为一次主请求及其捎带请求跑完的判断条件。我觉得不断升级主请求和我采用的方法实现起来的结构是基本一样的。由于在电梯开关门以及每到一层都可能会输入能够捎带的请求,所以在每一次电梯开关门上下层,我都会判断一次是否有能够捎带进来的请求。这种方法会有不停的对请求队列遍历去查找的问题,不过貌似对40条指令的遍历并不会太占CPU。然而,大闸蟹还是在强测中因为一个小小小小小小小的问题,被炸掉了一大半0.0。在判断捎带的时候,我采用的是判断读到的请求的进入楼层>=当前楼层并且方向向上,以及进入楼层<=当前楼层并且方向向下这样的条件来判断的,为了节省字符数,满足checkstyle,我将这些if分开嵌套,然后就出现了在刚刚上去到达的楼层开始有一堆请求要向下,但是这些请求进的都是>=的那个if,导致并没有带着他们走下去0.0。
对于优化,我想了这几种策略。一是在去接主请求的路上可以把能够在主请求楼层之前出去的请求先送过去,以此来解决那些上来就16-1,后面却跟着一些1-2,1-3之类的测试点。对于寻找主请求的时候,如果只是找第一条作为主请求,那么15-1,16-1,17-1这样的就会不停的上下,所以在寻找主请求的时候我也尽可能让它一次性能够覆盖到足够多的请求,来节省时间。(然而再怎么优化,由于上面那个小小小小小小小的问题我还是凉了)。
类图如下:
度量分析如下:
可以看出,架构基本和第一次一样,但是由于我在每一次开关门以及每一层上下楼都去判断是否有捎带的请求,导致电梯线程的复杂度较高。
第三次作业:
第三次作业是三台电梯一起运行,各自有各自的可达楼层,有各自的载客量,对于某些请求,还需要让乘客换成(这难道不是乘客应该考虑的事情吗2333,还有那诡异的残次品C电梯以及只有C才能到达的神秘三楼)。我大致采用了之前的架构,在get方法中增加了对于电梯名字以及是否是该架电梯可以送的人的判断。为了处理换乘问题,我新建了一个类,其中包含两个PersonRequest对象,用来拆分开需要换乘的请求。在执行一条请求时,判断其中第二个PersonRequest对象是否为null,不是的话就将其压到调度器的队列里面。
怎么样让电梯停下来是我本次作业遇到的最难的问题。由于我之前都是往里面压null,读到了才停,而这一次,如果依旧是读到null停的话可能会在之后分来出来的请求无法执行,会被炸掉。于是我就想让读到null不停下也不拿走,而是更改标志位空跑,知道分离过的请求全部跑完才去停。然后我就发现会有一个请求一直在跑null= =。既然这样,那我就让null被拿走,然后输入null的时候多输入两个,我以为这样三个电梯都会读到null停下来,然而,有一个电梯会把所有的null都抢走然后就没有然后了= =。最终,我发现,为什么要在输入的时候搞进去null呢,我为什么不再所有要分离的指令都搞定的时候再插入null让电梯停下来...菜到流泪(⊙﹏⊙)
然而,最让我想不到的是,又有一个小小小小小小小的bug炸掉了我强测一大半的点。我在有一个电梯中用来计人数的计数器忘记了清零= =。而且所有炸我的还全部是随机生成的数据,甚至被同学hach14次也全部都是随即数据乱炸0.0。de掉这个bug甚至用了一天。真是写程序不细心,debug两行泪 ┭┮﹏┭┮
类图如下:
度量分析如下:
可以看出,架构其实还是差不多的,只是原来是用PersonRequest现在变成了我自己新建的类,并且输出采用了加锁的静态方法。
由于和第二次架构基本一样,复杂度高的地方也基本一样。
BUG分析:
这三次作业,我自己的bug大都是不细心导致的。(然而都是小bug却导致了大问题,非洲人无话可说0.0)
对与寻找别人的bug,第一次作业,大家都不怎么会测并且傻瓜电梯实在是难出bug,随意根本没有hack到别人。第二次作业,我大部分采用的是我自己遇到的bug,比如停不下来,比如某些捎带请求拿了进去却没办法跑,成功发现了一台能够上天堂的电梯和一台吃人的电梯。第三次作业,我认为大家跑应该是没有问题的,大部分的问题是某些情况停不下来(然而我自己却是跑丢了0.0,或许我找的方向不怎么对),于是我采用了随机数据去测试其他人看能否停下来,停不下来才交,结果只发现了很少的错误,而同组的barsaker是直接交随机数据,结果却炸掉了好多好多人,(虽然全部是同质)。
体会与感想:
这一单元给我的感想是,写的时候开开心心,出分的时候想沙河主楼跃解千愁。但不管怎么样,对于多线程还是逐渐理解了的。多线程的这种不确定性,bug的不可复现性也大大大大大大锻炼了我们的debug能力。这一单元的测评还是比较严格的,虽然像我这种的非到小bug炸全家的不多见,并且好像也找不出什么能够拯救我这种每次都是因为一点点点点小bug被大面积炸的可怜孩子(非洲人无话可说)。
总而言之,这一单元是有挑战的一单元,也是极大锻炼我们能力的一单元。