OO第二单元总结
OO第二单元总结
同步块的设置与锁的选择:
-
第一次作业
由于第一次作业是单电梯,而且电梯的工作分为三种模式,各种模式人员的到来情况各有不同,完成思路方面想法比较简单,即完成双线程,一个用户输入线程,另一个电梯工作线程,使用生产者——消费者模式,两个线程共享一个Request对象,Request是一个存放用户需求的队列,用户输入需求则分模式按照不同的规则将需求入队,电梯则不断从Request队列中取出请求进行处理,对于该共享的Request对象采用synchronized同步块,同时对于电梯线程和用户输入线程在对于Request队列进行入队和出队操作时加synchronized锁,采用wait/notifyall语句保证不会对于共享的requests队列同时进行写取操作从而产生线程不安全的问题。
-
第二次作业
第二次作业将单电梯改成了多电梯,由于有多部电梯,所以对于电梯的调度有了要求,在这次作业中,我除了安排了用户输入线程和电梯工作线程之外,还又一个调度器线程,用来将用户输入的指令分配给不同的电梯进行工作。在用户输入线程和调度器线程之间,共享了一个Request队列对象,将该共享对象作为synchronized同步块,在两个线程之中分别对于该对象加上synchronized同步锁。同时在调度器线程和每一个电梯线程之间,又共享了一个ExcutingList队列对象,该队列中存放的是该部电梯所要处理的用户的请求,对于这些共享对象同样写为synchronized同步块,同时在各线程中对于对队列进行写或者取的操作进行加synchronized锁处理,和第一次作业一样,采用wait/notifyall语句保证不会产生线程不安全的情况。
-
第三次作业
第三次作业仅仅是对于电梯的型号做出了改变,所以改变的仅仅是调度器对于指令的调度策略,对于第二次整体的架构没有什么影响,同样的对于同步块和锁的设计同样与第二次作业没有任何区别,在这里就不再多做赘述。
调度器设计:
-
第一次作业
第一次作业由于是单电梯,没有设计调度器,在用户输入之后直接将请求加入共享的Request队列之中,而电梯直接从Request队列中将用户的请求取出进行处理,没有必要使用调度器。
-
第二次作业
第二次作业增加了电梯的台数,这也就需要对于多部电梯进行调度,即将从用户输入的不同请求分配给不同的电梯进行处理,而这一部分工作的实现就需要我们的调度器。在调度器中,我们有着与用户输入线程共享的Request队列对象,同时有着与所有的电梯线程各自共享的一个ExcutingList队列对象数组,我们的调度器Scheduler所要做的就是将Request队列中的请求取出并且按照一定的调度规则将其分配给每一个电梯的处理队列中进行处理,除此以外,Scheduler中还包含一个inputOver属性,当用户输入结束后,该属性被设置为true,电梯线程在各自的ExcutingList中的请求处理完成后,会判断inputOver这个属性是否为true,如果为true,则跳出while循环,结束线程,所有线程均结束后,程序结束。
在该单元中,电梯的运行模式被分为了Random类型、Morning类型、Night类型三种,对于三种模式调度器分别采用了不同的调度策略。对于Random模式,从用户输入线程每输入进来一条请求,该线程立刻将该条指令入队,Scheduler线程将该指令立即取出,根据各电梯的状态,比如说所在楼层,是否在执行请求,还有几条请求有待处理,进行比较后根据判断将该指令加入到某一部电梯的处理队列中等待处理。对于Morning模式,由于人们都是从一楼出发,而且到来的时间间隔不长,所以在这种模式下,在用户输入线程中,直接等待输入满了6条请求(6条指令是因为每部电梯限载6人)直接将6条请求一起放入Request队列中,调度器将这六条请求去除后,根据目标楼层的高低将这些请求从低到高重新排列,接着便与Random模式相同,根据各个电梯不同的状态按照一定的规则将这六条指令一起交给同一个电梯的处理队列进行处理。最后是Night模式,在Night模式下,所有乘客的请求同时到来,与Morning模式类似,直接读取用户输入线程的6条请求将这六条指令加入Request队列,接着Scheduler将Request队列中的6条指令请求的启示楼层按照从高到低的顺序重新排列,再将这六条指令加入到经计算决定好的某部电梯的处理队列之中。
-
第三次作业
第三次作业其实相较于第二次作业没有什么大的改动,只是对于电梯的型号有了更加细致的要求和规定,对此,调度器相较于第二次作业作出的改变不过是加入电梯队列策略的改变,由于不同型号的电梯速度也不尽相同,所以对于起点和终点终点都处在范围1楼到3楼和18楼到20楼的乘客,优先使用C类型的电梯,如果C类型电梯正全部出于busy状态,则进一步判断如果起点和终点楼层都在奇数层的乘客请求,使用B类电梯,如果B类也处于busy状态,则使用最慢的A类,如果所有的电梯都处于工作状态,则仍将该请求加入C类电梯的队列之中。对于其他类型的请求同样有类似的方法来将他们加入到不同的类型的电梯队列中,在这里不做过多的赘述,总之,第三次作业中调度器的作用与第二次作业一致,只是对于调度的策略进行了改变。
第三次作业架构分析:
- 由于三次作业每一次作业基本上都是在上一次作业的基础上完成的,基本上没有进行过重构,所以就以最后一次作业的架构进行分析并分析其可拓展性。
- UML图:

- UML协作图:

-
可拓展性分析:
在这一次作业中个人感觉架构还是比较清晰的,使用了调度器对于请求进行分配,如果要使用不同的调度策略直接对调度器进行修改即可,不同型号的电梯也只需要对于电梯类进行修改即可,可拓展性还是可以的。
自己程序的Bug:
-
第一次作业
第一次作业在强测中超时了很多点,硬要说也不是Bug,实际上是性能太差了,这是因为在第一次作业中,对于同步块和锁还不是很熟悉,所以对于用户输入线程和电梯线程两个run函数全部都做了加锁处理,所以实际上第一次作业写了一个单线程,自始至终只有一个线程在工作,这也就导致了性能的糟糕,除此以外,在这次的电梯作业中,对于Random模式没有写捎带,所以在Random模式下电梯是一个请求一个请求处理的,处理完了一个,再读取下一个进行处理,这会超时也就不奇怪了。
同时,在完成作业的过程中,还遇到了程序无法结束的Bug,这是由于在用户输入线程输入完成后,在对inputOver属性赋值后没有对于wiat中的线程进行唤醒,导致程序出现了死锁导致的。
-
第二次作业
在第二次作业中,在强测的同样超时了几个Random点,这几个Random点,请求的输入时间都是在170秒左右,也就是说,只有40秒的时间来完成所有请求的处理,我处理Random模式时由于不能够确定第二条请求什么时候来,所以第一条请求来后便直接将该请求加入队列之中进行处理,而这样对于请求都是同时输入的情况来说是非常影响性能的,很容易就会产生超时的问题。
-
第三次作业
在第三次作业的强测中,也是在Random模式下超时了几个点,超时的原因在于Random模式下捎带乘客写得有点问题,导致了捎带乘客会出现问题,会出现不捎带的情况,导致程序运行时间过长,从而超时。
除此以外,这次作业的强测中还出现了Morning模式下最后输入的几条请求不进行处理的问题,这是由于在Elevator类的run函数中,判断结束的条件放在了处理完请求之前,解决这个Bug只需要调换一下顺序即可。
测试他人程序的Bug:
在这第二单元的多线程电梯作业中,由于写的是一个实时的交互系统,输入需要按照时间输入,所以构造数据比较麻烦,而且每一个人的调度策略不同,输出肯定也不可能相同,这也就导致了对于他人程序的输出结果正确性的判断也比较困难,所以在这个单元互测过程中我采用了手动构造测试样例的方法(大部分是测试自己程序时候使用的数据样例,分三种模式分别构造一些简单可以捎带的样例,来看看别人程序的捎带有没有什么问题,还有就是改变输入时间,主要看看会不会出现死锁或者程序不会结束的问题。可惜的是,在这三次作业的互测中均没有发现他人的Bug。
心得体会:
线程安全:
-
对于共享对象,合理使用synchronized锁,避免出现同时读写从而导致的线程不安全问题,同时,也不能够为了避免线程不安全从而对于有着共享对象的几个线程的run函数全部加上synchronized锁,从而导致了性能的下降。
-
wait/notifyall方法提前想好写好,如果出问题了Debug比较麻烦。
-
结束条件的判断也要在动手写之前提前思考和规划好,如果后面再加的话,很容易会逻辑混乱产生问题,比如我在第一次作业的时候完全忘记了要结束程序这件事,后续再在代码里加的话,还是费了很大的功夫的,而且思路不是很清晰。
层次设计:
- 本单元作业中对于用户输入线程和电梯线程两个线程的run函数里面有很多代码部分都是可以封装写得更加层次化的,但是由于比较懒,就全部堆叠在一起了,导致run函数极度复杂,也不好修改或者Debug,所以说层次化的设计是十分重要的。

浙公网安备 33010602011771号