OO第二单元总结博客
OO第二单元博客
一.三次作业的设计策略
- 第一次作业:从第一次作业的电梯来看,对电梯的需求是多线程的单步电梯,因此我的设计是使用生产者消费者模式,除了主线程外设计两个线程,分别为生产者:乘客提出的请求;以及消费者:电梯的线程。由于只有一部电梯,单纯的生产者——消费者模式会导致的是电梯只能处理单一的请求,而且如果使用携带原则,会导致的是被携带的请求会被同时处理,从而对队列产生改变,因此需要维护一个PersonRequest的队列,实时对队列进行处理。为了避免电梯请求和连携规则同时对队列进行改变,要在改变的地方加上锁。设计电梯elevator类,同一时间只处理一个目标请求,对于连携请求进行判断决定是否在楼层停靠,从而达到连携的目的。
- 第二次作业:这次作业添加了电梯的数量,对于我设计的调度结构并没有需要重构的部分,改变就是从一开始的单个生产者单个消费者变成了单个生产者对多个消费者,多个电梯对于同一个请求队列进行处理,由于没有可到达楼层的限制,各部电梯没有区别,因此为每个乘客请求添加是否已被处理的状态变量,已经被其他电梯取走或连携规则去走的请求不再出现在可取的队列中。
- 第三次作业:第三次作业中添加了不同电梯的可到达楼层,因此必须设计相应的换乘机制,我在这次作业中采用了最近换乘原则,强调若是电梯无法到达目标楼层则查找其他几部电梯的可到达楼层,若是换乘可到达目标楼层,则返回最近的换乘楼层,同时再让乘客下电梯之后再请求队列中添加一个初始楼层为换乘楼层的新请求,同时为了限制一个乘客再某一层的某部电梯中不断上下,添加了限制条件,要求同一乘客只能乘同一部电梯一次,虽然可能造成效率较低,但是不容易出现线程故障。
二.功能和性能的平衡性
在这三次的作业中,我更加偏重了功能的全面性,比如乘客的反复乘坐,多人目标楼层相同或初始楼层相同,同一层人数超过限制导致目标请求无法执行东情况,目的在于尽量快速的让先提出请求的乘客到达目标楼层,而没有采用更加看重效率的总体调度方法。然而在第三次作业中,电梯的功能性和性能均不佳,原因在于我所采用的消费者方法和电梯的执行方法高度耦合,导致电梯的调度算法难以展开,从而必须采用大量加锁以保证线程安全,由于所有电梯公用同一个请求序列,这导致请求的处理效率十分低下,没有很好的发挥出多线程的优势所在。
三.基于度量来分析自己的程序结构
第一次作业:
Method |
ev(G) |
iv(G) |
v(G) |
unit21.Channel.Channel(Queue) |
1 |
1 |
1 |
unit21.Channel.get() |
1 |
2 |
3 |
unit21.Channel.put(PersonRequest) |
1 |
1 |
1 |
unit21.Consumer.Consumer(Channel,Queue,Stop) |
1 |
1 |
1 |
unit21.Consumer.Stop() |
1 |
11 |
13 |
unit21.Consumer.goDown(int) |
6 |
8 |
9 |
unit21.Consumer.goTo(int) |
1 |
4 |
4 |
unit21.Consumer.goUp(int) |
6 |
8 |
9 |
unit21.Consumer.run() |
3 |
7 |
8 |
unit21.Elevator.Elevator(int) |
1 |
1 |
1 |
unit21.Elevator.getClient() |
1 |
1 |
1 |
unit21.Elevator.getFloor() |
1 |
1 |
1 |
unit21.Elevator.setClient(PersonRequest) |
1 |
1 |
1 |
unit21.Elevator.setFloor(int) |
1 |
1 |
1 |
unit21.MainClass.main(String[]) |
1 |
1 |
1 |
unit21.Producer.Producer(Channel,Queue,Stop) |
1 |
1 |
1 |
unit21.Producer.addList(PersonRequest) |
1 |
1 |
1 |
unit21.Producer.run() |
3 |
4 |
4 |
unit21.Queue.Queue() |
1 |
1 |
1 |
unit21.Queue.State(PersonRequest) |
1 |
1 |
1 |
unit21.Queue.getQueue() |
1 |
1 |
1 |
unit21.Queue.inElevator(PersonRequest) |
1 |
1 |
1 |
unit21.Queue.inQueue(PersonRequest) |
1 |
1 |
1 |
unit21.Queue.isEmpty() |
1 |
2 |
2 |
unit21.Queue.outElevator(PersonRequest) |
1 |
1 |
1 |
unit21.Queue.outQueue(PersonRequest) |
1 |
1 |
1 |
unit21.Stop.isStop() |
1 |
1 |
1 |
unit21.Stop.stopInput() |
1 |
1 |
1 |
第二次作业:
Class |
OCavg |
WMC |
unit22.Channel |
1.33 |
4 |
unit22.Consumer |
4.67 |
42 |
unit22.Elevator |
1 |
5 |
unit22.MainClass |
5 |
5 |
unit22.Producer |
2 |
4 |
unit22.Queue |
1.6 |
24 |
unit22.Stop |
1 |
2 |
第三次作业:
Class |
OCavg |
WMC |
unit23.Channel |
1.67 |
5 |
unit23.Consumer |
5.33 |
64 |
unit23.Elevator |
1 |
6 |
unit23.MainClass |
1 |
1 |
unit23.Producer |
3 |
6 |
unit23.Queue |
1.6 |
24 |
unit23.Stop |
1 |
2 |
从类图以及分析数据可以看出,我的程序在三次作业中并没有进行重构,始终保持着生产者——消费者的基本模式,只是在电梯运行的规则以及判断标准上进行了改善。而我的问题也十分明显,再不重构的基础上导致我的控制电梯的消费者类变得耦合度很高,不断添加判断条件也使得自己的主要方法长度超标,不够灵活多变,比如要添加相关的细节需要进行方法的重构。而这种以增加判断条件为主要方式的电梯,由于在进行判断时队列不能被改变,因此在判断是也要为队列加锁,这就导致了访问效率十分低下,如果采用队列实时传递出参数的方法可能会提高访问效率。
四.程序中存在的bug
除去性能方面的问题,在这一单元程序也出现了很多的bug,其中前两次作业出现频率最高的是线程故障,也就是线程安全问题。在前两次作业中,我写的程序在本地测试的时候出现了很多线程之间协调的问题,出去同时访问统一变量的线程问题,更多的是对于统一请求的争抢问题。在我设计的电梯中,多部电梯会同时从请求队列中取出请求,这其中有多个状态:请求进入电梯,乘客进入电梯,乘客离开电梯。在这几个状态中很容易出现电梯所访问的请求消失或者被其他电梯抢先,以及电梯抢乘客导致空跑和空开门的状况,很容易造成访问错误等问题。因此为了限制这方面的bug我采用的方法是在每一个电梯判断的访问中也加上锁,虽然这样会造成性能的降低,但是保证了访问队列的安全和稳定,在检测完队列状态后尽快释放锁,以保证效率的提高。在第三次作业当中,产生了很多rte的bug,主要原因还是换乘机制的不完善,判断规则过于简单粗暴,很可能出现对于多次换乘等情况的考虑不周到,需要进一步改善判断条件以进一步完善。
五.发现别人bug的策略
没有发现别人的bug。
六.心得体会
多线程的程序和一般的程序有很大的区别,多线程所具有的不稳定性是其他类型的程序所没有的,而多线程的bug出现的方式也是之前我们从未遇到的。多线程其实教给了我们一种编程的新思路,其中所涉及到的包括生产者消费者类以及观察者类等新的模式,会对以后的工程化编程带来很大的好处。多线程有很多优势是之前的程序没有的,对于同一问题可以多个工具同时执行,并且进行统一控制,和以前的单个程序顺序执行具有很大的不同。虽然会占用很多的运行时间和CPU,但是面对对多个对象一类的情况依然是不可或缺的一部分。