其他分享
首页 > 其他分享> > 第二单元总结

第二单元总结

作者:互联网

第二单元总结

同步块的设置和锁的选择

第一次作业

在动手写第一次作业前,我认真阅读了第一周作业当周的实验代码,觉得实验的代码写的非常好,决定以实验代码为模板,完成第一次作业。第一次作业中,但凡某个方法涉及到了共享对象,无论是读还是写,我都给该方法加了一个synchronized的锁,且所有有synchronized锁方法的最后,我都加了notifyAll语句,处理方法非常暴力,不会出现线程安全的问题,但效率比较低。

第二次作业

有了第一次作业的积累和经验,我认识到我在第一次作业中的做法非常不好,存在的问题如下:

我在第二次作业中一一解决了上述的问题,只对存在写共享对象操作的方法上了锁,在保证线程安全的前提下,尽可能缩短了同步块中的代码数量,仅保留了必要的notifyAll,删除了无用的notifyAll语句。

第三次作业

在由第二次作业向第三次作业的迭代过程中,我对电梯线程并没有做过多的修改,改动较大的部分在“调度器设计”一节中会有涉及。

第三次作业对线程安全的处理,基本沿用了第二次作业的方法,可以说是基本没有做改动。

架构设计的变化和UML类图

本单元三次作业,我均采用了“生产者--消费者”的模式。

第一次作业

第一次作业中共有:主线程,输入线程,搬运线程和电梯线程五类线程。

RequestQueue类相当于托盘类,第一次作业共有6个托盘,一个主托盘,五个楼座各拥有一个分托盘,输入线程向主托盘放入请求。Schedule类为搬运线程,负责将主托盘中的请求按类别放入五个分托盘中。Elevator类为电梯类,是消费者,负责处理各分托盘中的请求。
image

第二次作业

在需求方面,第二次作业同第一作业相比新增了横向的电梯类型。

在代码方面,第二次作业同第一次作业新增了CrossSchedule类,用于搬运横向电梯处理的请求;新增了CrossElevator类,为横向电梯类,处理楼座之间移动的需求。

第二次作业共有1+5+10 = 16个托盘,一个为主托盘,十个为1至10层横向电梯的托盘,五个为A至E各楼座纵向电梯的托盘。输入线程向主托盘放入请求,由Schedule和CrossSchedule从主托盘分类搬运请求,再由横向电梯和纵向电梯处理请求。
image

第三次作业

第三次作业同第二次作业相比,新增了FloyInGraph类、SmartPeople类和Counter类。由于第三次作业新增了换成请求,这导致有些请求可能无法通过一部电梯完成,需要多部电梯才能处理。Counter类负责记录目前尚未完成的请求的数量;FloyInGraph类负责计算出乘客请求的最短路径;SmartPeople类负责记录计算出的最短路径。

大体处理流程如下:

输入线程输入请求后,立刻计算出完成该请求的最短路径,并记录,将其放入主托盘中。搬运线程将主托盘中的请求分类搬运至各分托盘中。各电梯开始处理分托盘中的请求。
image

未来扩展能力

如新增既可横向移动又可纵向移动的全能电梯,在第三次作业的基础上仍可进行拓展。

可新增全能电梯类,当全能电梯到达某座某层时,如接下来需进行纵向移动,即可询问该楼座纵向电梯的托盘,查看是否有可捎带乘客;如接下来需进行横向移动,即可询问该楼层横向电梯的托盘,查看是否有可捎带乘客。

调度器设计

第一次作业

第一次作业只有纵向电梯,且电梯数量固定,没有新增电梯的请求,我就将调度器和电梯合并到了一起。电梯接收到主请求后,立即前往主请求楼层去接乘客。在运行过程中,每到达新的楼层,先询问电梯中是否有需要出电梯的乘客,然后询问请求队列是否存在可以捎带的乘客,可以捎带的条件为:电梯目前没有满员;该乘客的出发方向与电梯当前运行方向一致。电梯接到主请求的乘客后,即前往主请求的目标楼层,运行过程中电梯的行为与上文所述保持一致。完成主请求后,便处理电梯中被捎带的乘客,将所有的乘客送达目的地后,开始接收下一次主请求。当请求队列为空,电梯内无乘客,且输入未结束时,电梯线程陷入等待;电梯线程完成的条件是:请求队列为空,电梯内无乘客且输入结束。

第二次作业

第二次作业新增了横向的电梯类型和新增电梯的请求。在多部电梯的调度策略上,我选择了自由公平竞争的策略。即同一楼座的所有纵向电梯或同一楼层的所有横向电梯共享一个相同的请求队列,每个电梯的行为同第一次作业中电梯的行为相同。线程刚开始时,所有电梯同时向主请求楼层移动,先到达楼层的电梯搭载乘客,其他电梯不做搭载行为,相当于陪跑。每一个电梯均为一个独立的线程。

第三次作业

第三次作业新增了乘客换乘请求,即有些乘客无法只通过搭载一次电梯便到达目的地。在第二次作业到第三次作业的迭代中,电梯部分并未做较大改动,改动主要体现在乘客上。将整个新主楼视为一个具有50个节点的图,各座的各层均为一个节点。将电梯视为路径,每次新增电梯后便对图进行一次更新。当请求被输入后,用迪杰斯特拉算法算出乘客的最短路径,新增了SmartPeople类作为PersonRequest类的子类,该子类新增了路径属性,用于存储乘客的路径。由乘客自己决定自己该去哪个楼座或哪个楼层的请求队列。这样做,可以不用对电梯做过多的改动。

自己程序的bug

我程序的bug主要集中于两个问题:线程安全问题和轮询问题。

线程安全问题出现在输出的线程不安全,一开始我没有理解指导书中输出线程不安全究竟是什么意思,没有对输出做特别的处理。后来我参考了实验的代码,新建了一个Printer的输出类,在输出类中对输出加了锁,才解决了输出线程不安全的问题。

轮询问题体现于CPU运行时间超时。为了解决该问题,首先要找出究竟是哪个线程出现了轮询。我在每个线程的开始处加了一条输出语句,输出线程的名字。然后用投喂包跑数据,看到当每个线程的名字大量循环出现时,便找出了轮询的线程。然后在该线程输出一些信息,查看轮询的原因。原因是当横向电梯的请求队列不为空时,横向电梯就会去接乘客,但并没有判断电梯在该楼座是否可以开门,如果不可以开门也仍然去接,到达楼座后便反复循环,直至可以在该楼座开门的电梯将乘客接走。我在电梯是否去接乘客前加了一个对能否开门的判断,解决了该问题。

发现别人程序bug所采取的策略

虽然课程组对互测的定义是白盒测试,即让同学们阅读他人的代码,从逻辑上找到他人代码的错误后,再编写相关测试数据进行hack,但我完全把白盒测试做成了黑盒测试,并没有下载他人的代码认真阅读,周日想着休息休息,就拿随机数据生成器生成一些测试数据,然后挑着顺眼的数据往里丢,其实跟赌博的本质一样,如果走运就能hack到倒霉的同学。

心得体会

线程安全

线程安全问题是多线程程序需要解决的重要问题。通过三次作业的实践,我逐步加深了对线程安全的认识和理解。线程安全的实现是通过同步实现的,不同步才会导致线程不安全。

我们可以对共享对象进行一些封装,为它实现一些线程安全的操作,使它成为一个线程安全的类。这样在外部对共享对象进行操作时,可以更加方便。

在加锁的过程中,也要注意什么时候需要加锁,加锁锁住什么的问题。盲目加锁确实可以保证线程安全,但会影响运行效率,这与多线程程序的本意相违背,需要综合考虑。

层次化设计

通过本单元的学习与实践,我认识到一个良好的设计可以很大程度上帮助完成代码的具体实现。

通过将需求层次化,可以简化每个层次的具体设计。多个简单的具体设计相比于一个复杂的具体设计,实现起来更为容易。本单元的三次作业均为输入请求,对请求进行分类和处理请求的流程。而该流程的三个步骤,又可继续进行细分,如对请求进行分类,可分为新增电梯请求和新增乘客请求;处理请求可分为横向电梯的处理和纵向电梯的处理等等。

我认为,层次化的设计是面向对象编程的重要组成部分。掌握层次化设计的思想,对后续的实践具有重要的指导意义。

标签:总结,请求,乘客,第二,作业,托盘,电梯,线程,单元
来源: https://www.cnblogs.com/Natt1e/p/16210623.html