Unit 2总结
一、第一次作业
本次作业采用生产者-消费者的架构,整个电梯系统主要由总调度器(Scheduled)、输入线程(InputThread)、电梯线程类(Elevator)、等待队列(PassengerQueue)构成。大致的实现过程包含以下两级:
1、InputThread——waitQueue——Schedule
这一级的功能为通过输入线程输入的数据,解析后放入waitingQueue中,总调度器之后能从waitQueue拿取乘客数据,在内部进行调度,分配给不同的电梯线程。这之中的共享对象为waitQueue。
2、Schedule——PassengerQueue——Elevator
这一级的功能为实现调度器与电梯线程之间的交互,因为第一单元每一座仅有一部电梯,故只需要在MainClass里面开五个PassengerQueueu作为共享对象,不需要额外的补充,所以我也在第一次作业的时候偷了点懒,以至于第一次作业代码的可扩展性较差(后面总是要还回来的)。
第一次作业的实现重点除生产者消费者模式之外,就是电梯线程的编写了。因为第一次作业相对来说比较简单,指导书中的要求也比较详细,需要自己额外扩展的东西不多,所以只要按照指导书就能写出来。个人感觉难点在于解决轮询的问题,轮询即是某一个循环在一直执行造成CPU资源使用过大,问题可能出在进程该被结束的时候,因为某一个条件未满足而无法结束,同时又没有其他的手段使得线程循环暂时停止的方法,因此产生了轮询。
有两种能解决轮询的办法,第一种相对来说比较取巧,即可以在条件不满足的时候使用sleep()语句来使循环进入等待。二是保证每一个while循环里面无论那一条执行路径上都会有使得线程进入等待状态的语句,这样同样能够解决轮询的问题。
第二个难点是捎带问题,如果严格按照ALS调度策略的话极有可能出现以下情况:第一位乘客需要从10楼到1楼,其他的乘客分别为1-10、2-10……使用ALS则会造成性能较差。
二、第二次作业
第二次作业与第一次作业相比加入了横向电梯,并且增添了增加电梯的需求,难度同样不大。
横向电梯的处理方法我采用的仍然是一个电梯类,电梯类中通过对电梯型号的判断分别运行两个不同的run方法,电梯的型号同样决定了电梯对应的PassengerQueue队列。
增加电梯需要在输入线程中进行修改,输入线程检测到电梯增添请求的指令时,找出电梯类型对应的PassengerQueue,同时还要根据floor和building来区分横向电梯和纵向电梯。
增添电梯后产生了多个电梯共享一个PassengerQueue的情况,我采用的是自由竞争的处理方法,为了解决资源的竞争问题,这里就不得不提到同步块与锁了
同步块与锁
先来看看下面的一段代码
同步块使用了synchronized锁,内部采用try-catch处理异常
在这一单元的过程中,我对锁的理解有以下两个方面
1、 所有的锁应该在被使用的类里完成而不应该让使用者来完成,这样大大提高了代码的可扩展性和安全性
2、 二是在拿取PassengerQueue队列里的乘客的时候,拿和删这两个操作一定要同步完成,不能让使用者先调用get方法,之后再调用remove方法,如果这么使用的话,极有可能造成两个线程同时拿到了同一个乘客,然后删去了两个乘客,造成WrongAnswer。
同时PassengerQueue中还提供了isEnd、isEmpty之类的方法,这些方法均需要上锁,避免产生问题。
三、第三次作业
第三次作业与前两次相比增添了电梯的定制化需求,同时乘客可以跨楼层和楼座发出移动请求。
电梯的定制化需求仅仅只需要修改原来的sleep中的数据和最大载客量即可,如果在前两次作业中可扩展性做的比较好,代码中不含“魔数”的话,实现起来是比较方便的。
跨楼层楼座移动需要使用到流水线模式,虽然我并不是完全这么做的,先说说我的做法吧。。。
我将原来的乘客类修改如下
原版:
新版
可以看到我将原来的startBuilding、endBuilding都替换成了ArrayList的形式,原因在于我可以在输入线程中读取到需要换乘的乘客时,根据我维护的一张电梯可到达楼层楼座图来规划出我的乘客换乘路线,并且保存在这几个数组中。同时,我将几个get方法修改成了读取ArrayList的第一位,这样我原来的代码并不需要改变,只需要在乘客出电梯门的时候判断乘客是否已经到达目的地,如果未到达目的地且当前需要换乘,就调用next方法将已完成的任务删去,这样重新get的结果就是原来ArrayList中的第二项,也即新的任务。在判断是否到达目的地我使用了变量nowNum和stageNum来记录,stageNum为输入线程设置的,nowNum为当前完成到了那一步,由电梯线程进行修改。
经过以上的处理之后,在将新的乘客直接丢入到waitQueue中,采用上述的方法可以不用建立一个新的乘客,产生变化的原因仅仅只是因为我的几个get方法拿到的数据发生了变化。这样就实现了跨楼层和楼座请求的换乘问题
UML类图
协作图
Bug分析
本单元出现了不少的bug,特别是在第一单元,有一个接乘客的循环里忘记加上电梯内乘客人数少于6的判断条件,导致被很多的数据hack了。
第二次作业中整体完成情况不错,但是仍旧出现了bug,问题出在自由竞争的时候两个电梯接到了同一个人,也即之前提到过的从队列中拿取数据和删除数据要同时进行。
第三次作业的问题出在忘记在电梯接乘客之前判断这一部电梯是否能够送到乘客的目的地(其实我写代码的时候是记得的,写着写着就忘了。。。)。因为这个错误WrongAnswer了很多的数据点。
发现Bug
可以通过在某一层同时投入大量的乘客来hack别人的电梯
心得体会
虽然是第一次接触到多线程的任务,但是在实验代码的指导下完成的相对来说不算特别困难,一些需要注意和理解的地方实验代码都已经给出了,整体架构只需要自己在其上缝缝补补以及完成电梯类线程的编写即可。
but这个单元的作业取得的性能分并不是很高,原因在我没有去优化我的调度策略,在第三次作业中也因为偷懒没有去使用dijstra算法。此外,在修改调度器后,对于共享变量的访问没有做到绝对安全,没有放在加锁区域,熟悉之后才发现了这些存在的问题。