OO_第二单元总结

第二次总结博客(电梯单元)

16071070 陈泽寅 2019.4.20

一:多线程实验初感

这个单元是多线程设计的实践单元,主要让咱们运用多线程的原理与思想去完成一个模拟电梯运行的策略。从最开始的单步电梯的傻瓜式调度,到第二次做业的单步电梯的捎带式策略,再到第三次做业的多部电梯捎带式运行策略。一次次的难度增强,也让咱们发现了多线程的使用规则和方法,而且在一次次的bug中更加体会到了锁的机制,以及各类并发机智的使用规则。虽然仍是有不少的问题,可是从这个单元确实学到了不少东西。java

二:3次单元做业的设计策略

第五次编程做业(单线程傻瓜电梯)

这个单元编程做业,我设计了两个线程类来完成所需的请求。分别为:输入请求类也就是咱们的生产者类,还有一个线程就是咱们的电梯,也就是消费者类。两个线程共享一个请求队列。输入请求线程是非阻塞的,它负责向请求队列里写入请求,而电梯线程负责从请求队列里读出请求。并执行相应的请求。python

相应的类图以下。linux

里面一共有三个类,其中Quene是咱们的共享队列,是一个资源,供InputElevator线程去争夺。所以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

时序图

  • 1: Input线程和Dispatcher之间的时序关系

从上图可知,Input每次来消息,写入到Quene中去,而后会向Dispatcher发起一个notify信号唤醒正在等待的调度器,告诉它有新的任务来了,他能够恢复调度。而后DispatcherQuene中去取心得请求,把它放到本身的队列中,并完成与Elevator的交互。以后若是没有其余新的请求,则它wait()。等待心得唤醒信号。

  • 2:Dispatcher线程和Elevator线程的时序关系图。

Elevator线程当本身主请求为空的时候,就进行wait(),等待调度器线程给其分派请求。Dispcther给其分派请求时,notify()这个电梯,其执行本身的主请求和副请求,一旦主请求结束,而且没副请求时,电梯notify()调度器,让它给本身分配新的请求。

  • 3:结束判断标志

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分析

此次做业基本符合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,若是其不需换乘,则直接将其从列表里移除。

类图:

时序图

时序图基本框架与第六次做业差很少

  • 1:DispactherInput的时序逻辑。

不一样的地方在于Dispatcher线程的结束条件为Input线程结束,而且本身的请求队列为空,而且全部的电梯的请求为空。这是由于有时候电梯运行完一个换乘请求还会把剩下的请求还给调度器,若是提早结束,换乘请求的后半段就没办法完成了。

  • 2:DispatcherElevator的时序逻辑交互

此时的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

SOLID分析

1:单一性原则:每一个类的职责明确,Input只负责输入,Elevator只负责根据请求来上下运行,而Dispatcher负责从队列里取请求分配给电梯。每一个线程之间的耦合性很小。

2:开放封闭原则:实现软编码,能够添加新的电梯进入到这个系统,而且能够在原基础上更改Dispatcher以修改调度策略。

3:里氏替换原则:因没有使用Extends所以不存在此问题

存在问题

  • 调度策略比较简单:优势是调度策略简单明了,可是因为调度的过程是静态的,即来了请求就将其拆分,而没有考虑实际的电梯位置以及电梯里的空间等因素,却少动态的考虑,致使一些请求的调度不理想,或者一些电梯空闲等待等不良状况的发生。

三:做业存在的BUG

第五次做业:

  • 存在问题

结束进程的判断失误。当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;
        }

这里的判断,当到了当前电梯的最底层时,方向向上,最高层时运行方向向下,就不会发生死循环的状况了。

4、验证方法:

为了模仿测评机的按时输入,我利用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()

5、学习心得与总结

  • 一、多线程是一个很是困难可是有意思的章节。咱们在学习多线程的时候要抓住一个大的方向,共享对象资源,当多个线程去抢夺一个资源时须要对那个对象的资源加锁,要搞懂synchronized的用法,而且不要盲目使用该方法,由于这会增长CPU没必要要的开销。

  • 二、同时,尽可能使用线程安全的容器,如vector,这样会减小线程冲突的可能性。
  • 三、合理使用wait(),notify()机制,减小CPU不必的浪费时间。

相关文章
相关标签/搜索