这个单元是多线程设计的实践单元,主要让咱们运用多线程的原理与思想去完成一个模拟电梯运行的策略。从最开始的单步电梯的傻瓜式调度,到第二次做业的单步电梯的捎带式策略,再到第三次做业的多部电梯捎带式运行策略。一次次的难度增强,也让咱们发现了多线程的使用规则和方法,而且在一次次的bug中更加体会到了锁的机制,以及各类并发机智的使用规则。虽然仍是有不少的问题,可是从这个单元确实学到了不少东西。java
这个单元编程做业,我设计了两个线程类来完成所需的请求。分别为:输入请求类也就是咱们的生产者类,还有一个线程就是咱们的电梯,也就是消费者类。两个线程共享一个请求队列。输入请求线程是非阻塞的,它负责向请求队列里写入请求,而电梯线程负责从请求队列里读出请求。并执行相应的请求。python
相应的类图以下。linux
里面一共有三个类,其中
Quene
是咱们的共享队列,是一个资源,供Input
和Elevator
线程去争夺。所以Quene
里的方法都应该写成同步方法,这样才能避免线程不安全的状况发生。时序逻辑以下。git
输入线程不断地向Quene里写入请求,Elevator
不断地从Quene
里取出请求而且执行。当Input
读入null结束请求时,便向Elevator
发出一个结束信号,此时Input
线程结束。Elevator
线程收到这个结束信号时,判断Quene
是否为空,不为空则继续取出任务执行完。直到Quene
也为空时则 Elevator
线程也结束并退出。shell
sourceFile | TotalLine |
---|---|
Dispatcher.java | 3 |
Elevator.java | 99 |
Input.java | 53 |
Quene.java | 44 |
度量分析:编程
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.close() | 1.0 | 2.0 | 2.0 |
Elevator.Elevator(Quene) | 1.0 | 1.0 | 1.0 |
Elevator.go(int) | 1.0 | 2.0 | 2.0 |
Elevator.in(int) | 1.0 | 2.0 | 2.0 |
Elevator.open() | 1.0 | 1.0 | 1.0 |
Elevator.out(int) | 1.0 | 2.0 | 2.0 |
Elevator.run() | 4.0 | 5.0 | 6.0 |
Input.getFlag() | 1.0 | 1.0 | 1.0 |
Input.Input(Quene) | 1.0 | 1.0 | 1.0 |
Input.main(String[]) | 1.0 | 1.0 | 1.0 |
Input.run() | 3.0 | 3.0 | 4.0 |
Quene.getList() | 1.0 | 1.0 | 1.0 |
Quene.getNum() | 1.0 | 1.0 | 1.0 |
Quene.requestGet() | 1.0 | 2.0 | 2.0 |
Quene.write(PersonRequest) | 1.0 | 1.0 | 1.0 |
Total | 20.0 | 26.0 | 28.0 |
Average | 1.33 | 1.73 | 1.87 |
代码分析:数组
咱们能够看到此次代码中,ELevator.run
的复杂度较高,这是由于在第一次多线程的过程当中没可以把代码模块化,致使电梯在运行的过程当中还承担了不少本不该该电梯来作的事情。其实在第一次做业中,因为没有涉及调度器,此时电梯还须要同时扮演调度起的角色,所以其复杂度会比较高。安全
代码缺陷:数据结构
第一次多线程实验中,尚未理解到线程wait()
和notify()
的好处与做用,所以并无使用等待唤醒机制,致使电梯进程一直在轮询判断Quene
里到底有没有新来的请求,这也致使当请求队列位空,可是输入进程尚未结束的时候,电梯线程一直在作无谓的动做,占据了大量的CPU时间,这是一个很愚蠢的设计,解决方法就是引入了等待唤醒机制,具体实施在下一次实验中。多线程
关于Solid原则
此次的做业没有很好的遵循SOLID原则,首先电梯线程参与了向Quene
索要请求的事情,违反了SRP原则。而电梯应该只需管本身的运行,应该把请求的分配交给Dispatcher
线程来管。同时也没有很好的践行ISP原则,没有实现接口。
在第六次编程中虽然仍是一部电梯,可是引入了捎带的策略。我吸收上一次编程中违反SOLID的教训,将
Elevator
线程与调度分开,从新设计了一个Dispatcher
调度器线程,其做用是从Quene
中获取请求,而且根据Elevator
的运行状态将其分配给电梯便可。而此次的电梯有一个主请求,和一个捎带请求队列。电梯无论怎么得到请求,它只管按照本身的请求去执行上楼下楼,上客下客的操做。请求的分配只归Dispatcher
来管。
电梯的数据结构以下。
private volatile int floor = 1; //初始化在1层 private volatile PersonRequest mainRequset = null;//主请求 private volatile Vector<Person subRequest = new Vector<Person(); private volatile int empty = 1;//主请求是否为空的标志 private volatile int inside = 0; private volatile int status = 1;//0 means down,1 means up
其中每一个电梯都有一个mainRequest
,和一个Vector类型的SubRequest
,而且每一个电梯会返回给调度器他的当前楼层,他的运行方向等信息,来让Dispatcher
完成新的调度。
能够看到最大的变化就是增长了一个Dispatcher
调度器类,这个类链接了Elevator
线程和Input
线程。它从Input
中获取请求,而后分配给Elevator
。
Input
线程和Dispatcher
之间的时序关系从上图可知,Input
每次来消息,写入到Quene
中去,而后会向Dispatcher
发起一个notify信号唤醒正在等待的调度器,告诉它有新的任务来了,他能够恢复调度。而后Dispatcher
从Quene
中去取心得请求,把它放到本身的队列中,并完成与Elevator
的交互。以后若是没有其余新的请求,则它wait()
。等待心得唤醒信号。
Dispatcher
线程和Elevator
线程的时序关系图。Elevator
线程当本身主请求为空的时候,就进行wait()
,等待调度器线程给其分派请求。Dispcther
给其分派请求时,notify()
这个电梯,其执行本身的主请求和副请求,一旦主请求结束,而且没副请求时,电梯notify()
调度器,让它给本身分配新的请求。
1:Input
结束标志为读到null
2:Dispatcher
结束标志为Input
结束而且Quene
以及本身的请求队列所有分配完成为空时。
3:Elevator
结束标志为Dispacther
结束而且本身当前的主请求以及副请求所有执行完毕。
Source File | Total Lines | Source CodeLines |
---|---|---|
Clock.java | 3 | 3 |
Dispatcher.java | 137 | 111 |
Elevator.java | 269 | 233 |
Input.java | 87 | 69 |
Person.java | 30 | 20 |
Quene.java | 51 | 33 |
Total | 577 | 469 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Dispatcher.check() | 1.0 | 6.0 | 6.0 |
Dispatcher.Dispatcher(Elevator,Quene) | 1.0 | 1.0 | 1.0 |
Dispatcher.getEndFlag() | 1.0 | 1.0 | 1.0 |
Dispatcher.isSubrequest(PersonRequest) | 4.0 | 6.0 | 7.0 |
Dispatcher.run() | 3.0 | 14.0 | 14.0 |
Elevator.arrive() | 1.0 | 1.0 | 1.0 |
Elevator.checkcurrentFloor() | 1.0 | 3.0 | 4.0 |
Elevator.close() | 1.0 | 1.0 | 1.0 |
Elevator.getCurrentFloor() | 1.0 | 1.0 | 1.0 |
Elevator.getMainRequset() | 1.0 | 1.0 | 1.0 |
Elevator.getStatus() | 1.0 | 1.0 | 1.0 |
Elevator.getSubRequest() | 1.0 | 1.0 | 1.0 |
Elevator.in(int) | 1.0 | 1.0 | 1.0 |
Elevator.loopOut() | 1.0 | 6.0 | 6.0 |
Elevator.method1() | 1.0 | 9.0 | 9.0 |
Elevator.open() | 1.0 | 2.0 | 2.0 |
Elevator.out(int) | 1.0 | 1.0 | 1.0 |
Elevator.run() | 4.0 | 10.0 | 12.0 |
Elevator.setMainRequset(PersonRequest) | 1.0 | 1.0 | 1.0 |
Elevator.up(int,int) | 1.0 | 17.0 | 23.0 |
Input.getFlag() | 1.0 | 1.0 | 1.0 |
Input.Input(Quene) | 1.0 | 1.0 | 1.0 |
Input.main(String[]) | 1.0 | 1.0 | 1.0 |
Input.run() | 3.0 | 7.0 | 8.0 |
Person.getInside() | 1.0 | 1.0 | 1.0 |
Person.getRequest() | 1.0 | 1.0 | 1.0 |
Person.Person(PersonRequest) | 1.0 | 1.0 | 1.0 |
Person.setInside(int) | 1.0 | 1.0 | 1.0 |
Person.setRequest(PersonRequest) | 1.0 | 1.0 | 1.0 |
Quene.addNum() | 1.0 | 1.0 | 1.0 |
Quene.getList() | 1.0 | 1.0 | 1.0 |
Quene.getNum() | 1.0 | 1.0 | 1.0 |
Quene.remove(int) | 1.0 | 1.0 | 1.0 |
Quene.setList(Vector<PersonRequest) | 1.0 | 1.0 | 1.0 |
Quene.setNum(int) | 1.0 | 1.0 | 1.0 |
Quene.subNum() | 1.0 | 1.0 | 1.0 |
Quene.write(PersonRequest) | 1.0 | 2.0 | 2.0 |
Total | 47.0 | 108.0 | 119.0 |
Average | 1.27 | 2.92 | 3.22 |
Elevator.up()
的复杂度比较高,缘由在于电梯的运行时须要判断每一层是否有要进出的客人,而且须要更新其楼层运行状态,所以复杂度较高。
此次做业基本符合SOLID要求,知足SRP要求,每一个线程只作本身分内的事情,
Input
只负责输入,Elevator
只负责根据请求来上下运行,而Dispatcher
负责从队列里取请求分配给电梯。每一个线程之间的耦合性很小。
1:单一性原则:添加了
Dispacther
类,使电梯线程再也不负责请求的调度。2:开放封闭原则:这点没有很好地实现,电梯在运行的地方不少都是硬编码,不能很好的实现扩展。
3:里氏替换原则:因没有使用
Extends
所以不存在此问题
1:存在的问题主要是线程与线程之间的通讯太过频繁,线程与线程之间的关联还不够分离。好比Input
须要和Dispatcher
交互,而Dispatcher
须要和Elevator
进行交互,频繁的交互使程序逻辑显得比较混乱。
2:优点,全部的地方取消了轮询查询,而是所有采起了wait()
,notify()
机制,减小CPU没必要要的执行时间,很好地迎合了多线程机制的规则。
此次是第三次多线程实验,相比较前两次实验此次实验使用了三部电梯,而且每部电梯有本身不一样的停靠楼层,不一样的搭乘上限,不一样的运行速度。这就不能采起以前的硬编码模式,须要对电梯的类进行改造。
电梯类:
private volatile int floor = 1; //初始化在1层 private volatile Vector<Person taskList = new Vector<Person(); private Dispatcher dispatcher; private String name; private long uptime; private int currentPersonNum = 0; private int space; private int[] stayFloor; private volatile int freeFlag = 1;
每一个电梯有本身的当前楼层,名字,本身的请求列表,本身的姓名,运行速度,电梯内空间与当前人数等私有属性。还有一个重要的私有属性,就是它的停靠楼层
stayFloor
,这是个一位数组,其在每一个电梯被建立的时候初始化。
int[] a = {-3, -2, -1, 1, 15, 16, 17, 18, 19, 20}; int[] b = {-2, -1, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; int[] c = {1, 3, 5, 7, 9, 11, 13, 15}; Elevator elevatorA = new Elevator("A", 400, 6, a); Elevator elevatorB = new Elevator("B", 500, 8, b); Elevator elevatorC = new Elevator("C", 600, 7, c);
这就初始化好了每个电梯的信息。
由于此次做业涉及换乘,所以调度策略发生改变,调度任务依旧由
Dispatcher
线程来执行。具体策略为每来一个请求,调度器就根据现有电梯的停靠楼层判断其是否须要换乘,若是须要换乘,则根据实际状况将这个请求拆分红两个请求。例如FR0M-2-TO-3,则须要先从2-1,再从1-3,拆分红两个请求以后,咱们封装一个类Person类。
private PersonRequest request1; private PersonRequest request2; private volatile int change = 0; //是否须要换乘 private volatile int inside = 0; private volatile int first = 0; private volatile int second = 0; private volatile int finishFirst = 0; //是否已经完成第一次请求,等待执行换乘请求。
每一个Person
对象记录者本身有哪两个请求,而且第一个请求是否已经执行完,调度器根据这个对象的信息,将其分派给相应的电梯,电梯执行完第一个请求,则将其还给调度器,让调度器继续将其分派给下一个电梯完成换乘任务。
public Person analyse(PersonRequest request) { Person person; int from = request.getFromFloor(); int to = request.getToFloor(); int id = request.getPersonId(); int[] listFrom = getList(from); //得到停靠from楼层的电梯 int[] listTo = getList(to); // 得到停靠to楼层的电梯 int coincide = isCoincide(listFrom, listTo); /** * public Person(PersonRequest request1, PersonRequest request2, * int change, int first, int second) */ if (coincide != -1) { // no need change person = new Person(request, null, 0, coincide, 0); } else { //须要换乘的状况 PersonRequest[] newRequest = newTwoRequest(listFrom, listTo, request); person = new Person(newRequest[0], newRequest[1], 1, listFrom[0], listTo[0]); } return person; }
这个函数就是完成请求拆分的方法,先得到一个请求的from和to楼层,而后看哪些电梯可以到达这些楼层,若是有重合的,则不须要换乘,不然找最快的两部电梯,到这两部电梯重合处进行换乘。生成两个新的请求。
再也不设定捎带请求,电梯根据本身的请求队列以及运行方向来进行运行状态的调整。若是当前运行状态向上,就会把请求队列里同方向的请求所有一次性处理,直到没有同方向的或者到达顶楼。若是一个请求执行完毕,而且其是个换乘请求则将其还给
Dispatcher
,若是其不需换乘,则直接将其从列表里移除。
时序图基本框架与第六次做业差很少
Dispacther
和Input
的时序逻辑。不一样的地方在于Dispatcher
线程的结束条件为Input
线程结束,而且本身的请求队列为空,而且全部的电梯的请求为空。这是由于有时候电梯运行完一个换乘请求还会把剩下的请求还给调度器,若是提早结束,换乘请求的后半段就没办法完成了。
Dispatcher
与Elevator
的时序逻辑交互此时的Dispatcher
不能提早结束,须得等到本身的请求队列为空,而且三部电梯请求队列也为空而且Input
线程向其发送End标识时,才能结束。
Source File | Total Lines | Source CodeLines |
---|---|---|
Clock.java | 3 | 3 |
Dispatcher.java | 291 | 246 |
Elevator.java | 320 | 276 |
Input.java | 91 | 70 |
Person.java | 61 | 45 |
Quene.java | 22 | 16 |
Total: | 788 | 656 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Dispatcher.analyse(PersonRequest) | 1.0 | 2.0 | 2.0 |
Dispatcher.check() | 1.0 | 6.0 | 6.0 |
Dispatcher.Dispatcher(Elevator[],Quene) | 1.0 | 1.0 | 1.0 |
Dispatcher.down(int,int,PersonRequest) | 6.0 | 10.0 | 12.0 |
Dispatcher.getEndFlag() | 1.0 | 1.0 | 1.0 |
Dispatcher.getList(int) | 4.0 | 3.0 | 5.0 |
Dispatcher.getQuene() | 1.0 | 1.0 | 1.0 |
Dispatcher.isCoincide(int[],int[]) | 4.0 | 1.0 | 4.0 |
Dispatcher.newTwoRequest(int[],int[],PersonRequest) | 7.0 | 11.0 | 13.0 |
Dispatcher.run() | 3.0 | 23.0 | 23.0 |
Elevator.arrive() | 1.0 | 1.0 | 1.0 |
Elevator.close() | 1.0 | 1.0 | 1.0 |
Elevator.compareFloor(int) | 2.0 | 1.0 | 2.0 |
Elevator.Elevator(String,int,int,int[]) | 1.0 | 1.0 | 1.0 |
Elevator.findMaxTask(int) | 1.0 | 6.0 | 6.0 |
Elevator.getCurrentFloor() | 1.0 | 1.0 | 1.0 |
Elevator.getFreeFlag() | 1.0 | 1.0 | 1.0 |
Elevator.getIndex(int) | 3.0 | 1.0 | 3.0 |
Elevator.getStayFloor() | 1.0 | 1.0 | 1.0 |
Elevator.getTaskList() | 1.0 | 1.0 | 1.0 |
Elevator.getUptime() | 1.0 | 1.0 | 1.0 |
Elevator.in(int) | 1.0 | 1.0 | 1.0 |
Elevator.method1() | 2.0 | 6.0 | 9.0 |
Elevator.open() | 1.0 | 2.0 | 2.0 |
Elevator.out(int) | 1.0 | 1.0 | 1.0 |
Elevator.outThisfloor() | 3.0 | 23.0 | 24.0 |
Elevator.run() | 5.0 | 8.0 | 9.0 |
Elevator.setDispatcher(Dispatcher) | 1.0 | 1.0 | 1.0 |
Elevator.setFreeFlag(int) | 1.0 | 1.0 | 1.0 |
Elevator.up(int,int) | 2.0 | 6.0 | 10.0 |
Input.getFlag() | 1.0 | 1.0 | 1.0 |
Input.Input(Quene) | 1.0 | 1.0 | 1.0 |
Input.main(String[]) | 1.0 | 1.0 | 1.0 |
Input.run() | 3.0 | 5.0 | 6.0 |
Person.getChange() | 1.0 | 1.0 | 1.0 |
Person.getFinishFirst() | 1.0 | 1.0 | 1.0 |
Person.getFirst() | 1.0 | 1.0 | 1.0 |
Person.getInside() | 1.0 | 1.0 | 1.0 |
Person.getRequest1() | 1.0 | 1.0 | 1.0 |
Person.getRequest2() | 1.0 | 1.0 | 1.0 |
Person.getSecond() | 1.0 | 1.0 | 1.0 |
Person.Person(PersonRequest,PersonRequest,int,int,int) | 1.0 | 1.0 | 1.0 |
Person.setFinishFirst(int) | 1.0 | 1.0 | 1.0 |
Person.setInside(int) | 1.0 | 1.0 | 1.0 |
Quene.getList() | 1.0 | 1.0 | 1.0 |
Quene.setList(Vector<PersonRequest) | 1.0 | 1.0 | 1.0 |
Quene.write(PersonRequest) | 1.0 | 2.0 | 2.0 |
Total | 79.0 | 146.0 | 168.0 |
Average | 1.68 | 3.11 | 3.57 |
1:单一性原则:每一个类的职责明确,
Input
只负责输入,Elevator
只负责根据请求来上下运行,而Dispatcher
负责从队列里取请求分配给电梯。每一个线程之间的耦合性很小。2:开放封闭原则:实现软编码,能够添加新的电梯进入到这个系统,而且能够在原基础上更改Dispatcher以修改调度策略。
3:里氏替换原则:因没有使用
Extends
所以不存在此问题
结束进程的判断失误。当Input
进程都到null请求时,发出Input
结束信号,Elevator
读到这个请求就结束了本身的线程。但其实队列中还存在请求没有执行完。
在电梯收到Input
线程结束的请求时,再判断一下请求队列是否还存在未完成的请求,若是没有,则电梯线程也结束,不然继续执行直到请求队列为空。
while (true) { if (Input.getFlag() == 1 && Quene.getNum() == 0) { break; }
仍然是Dispatcher
线程提早终止的问题。当Input
请求发出终止信号时,Dispatcher
去判断Quene
是否为空,若是为空,则Dispatcher
也终止。但实际上调度器本身的队列里还有请求没有分配给电梯执行。
在受到Input
的结束请求时,Dispatcher
增长一条对本身请求队列是否为空的判断,当input请求结束,Quene
请求队列为空,本身的请求队列为空时,向电梯线程发送Dispatcher
结束的信号。
while (true) { if (quene.size() == 0 && Input.getFlag() == 1 && requsetQuene.getList().size() == 0) { endFlag = 1; break; }
电梯运行状态的问题,电梯每次都会根据第一条请求肯定运行方向,但若是当第一条请求为顶层,而且电梯已经满,则电梯在20层时又会进行一次判断,但它仍是会向上,所以这是就会出现一个死循环。
当电梯运行到顶层或最低层的时候进行判断,改变其运行方向。
if(this.floor==stayFloor[stayFloor.length-1]){ k = -1; } if(this.floor == stayFloor[0]){ k = 1; }
这里的判断,当到了当前电梯的最底层时,方向向上,最高层时运行方向向下,就不会发生死循环的状况了。
为了模仿测评机的按时输入,我利用python以及linux的shell写了测试脚本,提取每条指令的时间,而后按时将指令放到管道中去,而后重定向到标准输入,模拟了在肯定时间输入的状况。
解析时间:
import time f = open(r"/Users/chenzeyin/Desktop/data",'r') temp=f.readline() sleeptime = [] req = [] lastTime = 0 while temp: temp = temp.strip("\n") gg = temp.split("]") gg[0]=gg[0][1:] sleeptime.append(float(gg[0])-lastTime) lastTime = float(gg[0]) req.append(gg[1]) temp = f.readline() sleeptime =[2.3,1.3] req=["1-FROM-2-TO-5","2-FROM-3-TO-4"] for i in range(len(req)): #print(sleeptime[i]) time.sleep(sleeptime[i]) print(req[i]) f.close()
测试
import os import time path = "/Users/chenzeyin/Desktop/czy/git/Elevator3/out/production/Elevator3" def ownTest(): os.chdir(path) os.system(r"python -u /Users/chenzeyin/Downloads/timeInput.py|java -Djava.ext.dirs=/Users/chenzeyin/Downloads Input/Users/chenzeyin/Desktop/out") ownTest()
一、多线程是一个很是困难可是有意思的章节。咱们在学习多线程的时候要抓住一个大的方向,共享对象资源,当多个线程去抢夺一个资源时须要对那个对象的资源加锁,要搞懂synchronized
的用法,而且不要盲目使用该方法,由于这会增长CPU没必要要的开销。
三、合理使用wait()
,notify()
机制,减小CPU不必的浪费时间。