第二单元是咱们学习oo以来第一次接触多线程。这一单元的三次做业和之前同样,采用了难度递进的方式,并且前两次做业的设计思路在第三次做业都多多少少有些体现(或者说是在其基础上作出的改进)。因此此次博客将以第三次做业为主,对这一单元的做业进行分析。算法
第一次做业是傻瓜电梯的设计,我就严格按照,先来先服务,一次只有一我的在电梯里来设计。第二次增长了捎带请求,也就是说,电梯再也不只能有一我的,而是有一个主乘客(主请求),和多个从属乘客(从属请求)。这里的主请求和第一次做业里的只有一个请求时候的那一个惟一的请求其实起到的是相同的做用,就是说实际操控电梯的其实是他,而电梯中的其他从属请求能作的只是在他要进入或者要出去的时候,命令电梯开关门供他进出。固然,从属请求也有可能成为控制电梯实际运行的主请求,那就是在主请求已经到达目的地,而从属请求尚未达到的时候。这两次做业其实大体上没多大区别,只是第二次做业在调度方法上作了改进,但整体上看,都是两个线程(主线程,在我这儿也就是输入线程,电梯线程)的并发。他们之间的关系与生产者消费者很类似,输入线程往请求队列里投放请求,电梯线程从请求队列里拿出请求,这个请求队列就是两个线程都会访问的临界资源,须要使用synchronized将其锁住,防止两个进程同时对其进行读写的状况发生。输入线程向请求队列输入请求是在任什么时候候均可以进行的,至于电梯线程何时从请求队列取出请求,那就是调度器的职责了。安全
接下来具体讲一下第三次做业的设计策略。与前两次做业不一样,此次做业在之前的基础上由一部电梯改为了三部电梯,并且由之前的电梯每一层均可以到达,变成了不一样电梯能够到达特定楼层。多线程
这样就会出现这种状况,有一些请求没法在一部电梯里完成,而是须要多部电梯配合完成。好比一个从-1楼到3楼的请求,电梯A能够到-1楼和1楼可是到不了3楼,电梯C能够到1楼和3楼可是到不了-1楼,这是单独的A、C都完成不了任务,可是让A、C合做,A把乘客从-1运到1,再让把乘客从1运到3,就完成了这个请求。因此一个最容易想到,也最容易实现的方法,就是A、B、C三部电梯各自有本身的请求队列,在输入一个请求时,由调度器决定这个请求应该去往哪一个队列。要是能用一部电梯就解决的请求,那就只用一部电梯,好比-1楼到1楼就用A电梯。若是是一部电梯没法解决,好比先前提到的-1到3楼的例子,那就把这个请求拆分红两个请求,一个是-1到1楼的请求,一个是1到3楼的请求,而后这两个请求分别进入A、C两部电梯的等待队列。这里还有一个问题,就是1到3楼必须在-1到1楼的请求执行完成以后执行,个人解决方法是每部电梯设置两个等待队列,一个是就绪队列,一个未就绪队列。就绪队列和咱们以前的队列没有分别。而未就绪队列中的请求都是从一个主线程输入的请求拆分红两个请求中的一个,而与它对应的另外一个请求在就绪队列中,只有等就绪队列中的这个请求已经执行完成后,这个未就绪队列中的请求才能进入就绪队列,等待调度器调度给电梯。仍是用-1到3楼的例子来解释,在这个例子中,我让1到3楼的请求先到C电梯的未就绪队列,-1到1楼的请求到A电梯的就绪队列,这时,调度器能够再合适的时机把-1到1楼的请求给A电梯,可是1到3楼的请求被分配到C电梯的等待队列,只有等-1到1楼的请求执行完了也就是说乘客已经从电梯出来,处于1楼了,它才能进入就绪队列,才有从1楼又进入C电梯的资格。架构
这种算法容易实现,可是只考虑了正确性的问题,它的性能是不好的。好比设想这样一种状况:一个从1楼到15楼的请求,调度器把这个请求分给了A电梯,在乘客进入A电梯,电梯关门后,又有一个1到15楼的请求到达请求队列,调度器又会把这个请求分配给A电梯,那么A只能把第一个乘客送到15楼,再返回1楼,把第二个乘客送到15楼。1到15楼的请求本来是A、B电梯均可以单独完成的,可是因为调度器的调度方式,是只根据请求自身而不顾电梯的状态来拆分请求。所以,虽然B电梯也能够完成这个任务,但因为调度方式的缘由,明明B没人用,A处于忙碌状态,可是调度器认定了A电梯,就一根筋的只把任务分配给A,致使了电梯资源的浪费。并发
1.代码统计性能
method ev(G) iv(G) v(G)学习
Dispatcher.Dispatcher() | 1.0 | 1.0 | 1.0 |
Dispatcher.geta() | 1.0 | 4.0 | 8.0 |
Dispatcher.getb() | 1.0 | 4.0 | 8.0 |
Dispatcher.getc() | 1.0 | 4.0 | 8.0 |
Dispatcher.getRequestsA() | 1.0 | 1.0 | 1.0 |
Dispatcher.getRequestsB() | 1.0 | 1.0 | 1.0 |
Dispatcher.getRequestsC() | 1.0 | 1.0 | 1.0 |
Dispatcher.getWaita() | 1.0 | 1.0 | 1.0 |
Dispatcher.getWaitb() | 1.0 | 1.0 | 1.0 |
Dispatcher.getWaitc() | 1.0 | 1.0 | 1.0 |
Dispatcher.put(PersonRequest) | 1.0 | 4.0 | 4.0 |
Dispatcher.requestsadc() | 1.0 | 2.0 | 3.0 |
Dispatcher.setExit() | 1.0 | 1.0 | 1.0 |
Dispatcher.sort1(PersonRequest) | 1.0 | 23.0 | 23.0 |
Dispatcher.sort2(PersonRequest) | 1.0 | 9.0 | 9.0 |
Dispatcher.sort3(PersonRequest) | 1.0 | 6.0 | 7.0 |
Dispatcher.waitsadc() | 1.0 | 3.0 | 3.0 |
Elevator.arrive() | 1.0 | 19.0 | 20.0 |
Elevator.call() | 1.0 | 5.0 | 5.0 |
Elevator.close() | 1.0 | 14.0 | 15.0 |
Elevator.downfloor() | 1.0 | 1.0 | 2.0 |
Elevator.Elevator(Dispatcher,int,int) | 1.0 | 1.0 | 1.0 |
Elevator.getFloor() | 1.0 | 1.0 | 1.0 |
Elevator.getThread() | 1.0 | 1.0 | 1.0 |
Elevator.in(int) | 1.0 | 1.0 | 1.0 |
Elevator.move() | 1.0 | 5.0 | 5.0 |
Elevator.open() | 1.0 | 2.0 | 2.0 |
Elevator.out(int) | 1.0 | 7.0 | 7.0 |
Elevator.run() | 1.0 | 6.0 | 6.0 |
Elevator.sleep() | 1.0 | 2.0 | 2.0 |
Elevator.start() | 1.0 | 2.0 | 2.0 |
Elevator.threadsleep(int) | 1.0 | 2.0 | 2.0 |
Elevator.upfloor() | 1.0 | 1.0 | 2.0 |
Main.main(String[]) | 3.0 | 3.0 | 3.0 |
Total | 36.0 | 140.0 | 158.0 |
Average | 1.0588235294117647 | 4.117647058823529 | 4.647058823529412 |
2.类图优化
运行Main类的初始进程便是三部电梯线程的父进程(建立三个电梯进程),同时扮演着输入线程的角色。他不断向请求队列(请求队列位于调度器中)输入请求。再有调度器处理这个请求,将处理后的1~2个请求加入到A、B、C的就绪队列和未就绪队列中。最后电梯线程执行到适当的时机,调度器会根据状况把这部电梯的就绪队列中的请求给到相应电梯来执行,或者将未就绪队列中的请求移动到就绪队列中。这种结构好处在于简单明了,只有三个类,分别负责请求输入,电梯运行和电梯调度,分工明确,思路简单。可是这种写法带来的弊端就是调度器类过于冗长,在写调度器的时候容易顾此失彼。并且因为调度器所有都写在一个类中,修改时要考虑的东西不少,改debug和优化带来了不小的难度。spa
多线程与单线程最大的不一样在于线程安全问题。单线程你只须要想着你在这一个线程中所要实现的功能,若是要上难度,也只会是在语法上上难度。而多线程不一样,你必须考虑整体架构,必须考虑线程间的通信,考虑不一样线程的同步与互斥。因此在设计时,咱们要考虑设计的原则问题:单一责任原则、开放封闭原则、里氏替换原则、接口分离原则、依赖倒置原则。线程