BUAA OO 第二单元总结

BUAA OO 第二单元总结

第一次作业

作业简介

简单的多线程实时电梯系统,五个楼座,10层楼,只有竖向电梯

StarUML图与类结构

其中,各个类的含义如下:

|- MainClass:主类
|- Elevator:电梯类
|- Queue:请求队列类
|- Stdin:输入处理类
|- Output:输出类

SequenceDiagram

架构分析

程序分析

本次作业,是我第一次接触多线程编程任务,整体架构参考了实验课的代码。

大体思路是,输入处理类接收输入包的指令输入,对指令进行分析,将指令分配给对应电梯的请求队列Queue(在这里分配的原则是,根据请求所在楼座,分配给对应楼座的电梯)。请求进入电梯的请求队列后,按照电梯运行策略进行运动。

电梯运行策略,我采取了指导书上的ASL策略,实现了捎带,捎带策略与指导书基本相同。

电梯运行,我设置了State表示电梯当前的状态:

enum State { Rest_state, End_state, Open_state, Close_state,
    In_state, Out_state, Up_state, Down_state }

并使用switch语句进行状态转换,框架如下:

public void run() {
    while (true) {
        switch (state) {
            case Rest_state: {
                ......
                break;
            } case Up_state: {
                ......
                break;
            } case Down_state: {
                ......
                break;
            } case Open_state: {
                ......
                break;
            } case Close_state: {
                ......
                break;
            } case In_state: {
                ......
                break;
            } case Out_state: {
                ......
                break;
            } case End_state: {
                return;
            } default: {
                break;
            }
        }
    }

parser方法主要是用于获取在当前层可以进入电梯的队列(uplist & downlist)与可以出电梯的队列(outlist),框架如下:

public int[] parser(ArrayList<PersonRequest> uplist, ArrayList<PersonRequest> downlist) {
    synchronized (queue) {
        int upnum = 0;
        int downnum = 0;
        int outnum = 0;
        for (遍历电梯请求等待队列) {
            PersonRequest request = 取一个请求;
            if (FromFloor为当前楼层) {
                if (请求为向上请求) {
                    该请求加入uplist,upnum+1
                } else if (请求为向下请求) {
                    该请求加入downlist,downnum+1
                }
            }
        }
        for (遍历已进入电梯的请求队列) {
            if (ToFloor为当前楼层) {
                该请求加入queueout,outnum+1
            }
        }
        return new int[]{upnum, downnum, outnum};
    }
}

捎带策略主要框架如下:

synchronized (queue) {
    if (电梯方向向上) {
        if (存在FromFloor为当前层的向上指令 || 当前层有人要出电梯) {
        	queuein设为uplist,电梯状态转换:->Open_state
        }
    } else if (电梯方向向下) {
        if (存在FromFloor为当前层的向下指令 || 当前层有人要出电梯) {
            queuein设为downlist,电梯状态转换:->Open_state
        }
    }
}

由于输出包线程不安全,我对于输出包进行了再封装,使用单例模式,建立Output类。

public class Output {
    private static final Output OUTPUT_THREAD = new Output();

    private Output(){}

    public static Output getInstance() {
        return OUTPUT_THREAD;
    }

    public synchronized void println(String msg) {
        TimableOutput.println(msg);
    }
}

由于此次电梯只有五个,且数目固定,我并没有建立电梯的list,而是直接在MainClass主类里面新建了5个电梯以及电梯对应的Queue,这显得主类有些臃肿,下次作业可以采用list的方式改掉。

同步块的设置和锁的选择

本次作业,共享资源只有每个座电梯的请求等待队列(被input和对应电梯,两个线程访问)和Output。

对于同步块的设置和锁的选择,我采用了简单的synchronized关键字。

Output类,我设置打印方法println为同步方法:

public synchronized void println(String msg) {
    TimableOutput.println(msg);
}

对于等待队列,我在需要使用它们的地方,设置同步块:

synchronized (queue) {
	queue操作(加指令、删指令或者其他)
}

性能优化

1、实现捎带策略。

程序分析

度量分析

class OCavg OCmax WMC
Stdin 5.0 9.0 10.0
Elevator 4.7272727272727275 21.0 52.0
MainClass 1.0 1.0 1.0
Output 1.0 1.0 3.0
Queue 1.0 1.0 4.0
Elevator.State 0.0
Total 70.0
Average 3.3333333333333335 6.6 11.666666666666666

OCavg = Average opearation complexity(平均操作复杂度)

OCmax = Maximum operation complexity(最大操作复杂度)

WMC = Weighted method complexity(加权方法复杂度)

可以看到,Stdin类和Elevator类复杂度较高,可能的原因在于这两个类都是线程类,且执行了整个电梯系统大部分的功能,功能较为复杂,时间开销大。

其余类的复杂度尚可。

方法复杂度分析

method CogC ev(G) iv(G) v(G)
Elevator.dispatch() 56.0 1.0 12.0 25.0
Elevator.parser(ArrayList, ArrayList) 10.0 1.0 7.0 7.0
Stdin.run() 8.0 3.0 9.0 9.0
Elevator.stay() 7.0 1.0 8.0 8.0
Elevator.in() 3.0 3.0 2.0 3.0
Elevator.run() 3.0 3.0 9.0 10.0
Elevator.close() 1.0 1.0 2.0 2.0
Elevator.down() 1.0 1.0 2.0 2.0
Elevator.open() 1.0 1.0 2.0 2.0
Elevator.out() 1.0 1.0 2.0 2.0
Elevator.up() 1.0 1.0 2.0 2.0
Elevator.Elevator(Queue, int, int, int, char, int, long, ...) 0.0 1.0 1.0 1.0
MainClass.main(String[]) 0.0 1.0 1.0 1.0
Output.Output() 0.0 1.0 1.0 1.0
Output.getInstance() 0.0 1.0 1.0 1.0
Output.println(String) 0.0 1.0 1.0 1.0
Queue.Queue(boolean, ArrayList) 0.0 1.0 1.0 1.0
Queue.getQueueisEnd() 0.0 1.0 1.0 1.0
Queue.getWaitQueue() 0.0 1.0 1.0 1.0
Queue.setQueueisEnd(boolean) 0.0 1.0 1.0 1.0
Stdin.Stdin(Queue, Queue, Queue, Queue, Queue) 0.0 1.0 1.0 1.0
Total 92.0 27.0 67.0 82.0
Average 4.380952380952381 1.2857142857142858 3.1904761904761907 3.9047619047619047

CogC = Cognitive complexity(认知复杂度)

ev(G) = Essential cyclomatic complexity(基本圈复杂度)

iv(G) = Design complexity(设计复杂度)

v(G) = cyclonmatic complexity(圈复杂度)

可以看到,dispatch调度方法复杂度较高,其余方法的圈复杂度尚可。

调度方法主要解决的问题是,请求从电梯请求等待队列进入电梯;捎带;电梯基于主请求的运行方向等问题,确实较为复杂,可能具体算法还有待改进,也可以考虑建立多个方法分摊一下。

Bug分析

程序设计过程中的bug

程序设计过程中遇到过很多bug,多数为细节处理的考虑不周造成的。

比如在第一次提交时,我遇到了RE的错误,经检查,发现CPU时间普遍很高。经过咨询助教,得知CPU时间一般在0-2s为宜,故锁定问题为存在轮询。经过分析代码架构,我发现我的程序每接收一个请求,会唤醒所有电梯,这是没必要的,A座的请求只需要唤醒A座的电梯即可。如果请求会唤醒所有电梯,则在大部分时间,电梯都会空转浪费CPU资源。改正此点后解决了RE的错误。

公测中的bug

HW1的公测中,我的程序没有被检测出bug。

互测中的bug

HW1的互测中,我的程序被检测出了多组bug,均为同质bug。其原因是,没有考虑输出包的线程不安全性,导致时间戳并非非减输出。改正方案为封装为Output类。

public class Output {
    private static final Output OUTPUT_THREAD = new Output();

    private Output(){}

    public static Output getInstance() {
        return OUTPUT_THREAD;
    }

    public synchronized void println(String msg) {
        TimableOutput.println(msg);
    }
}

HW1的互测中,我发现了房间中其他同学的bug,均为上述的输出包线程不安全的问题。

测试思路

测试思路是 坑点针对测试+随机测试

其中坑点针对测试需要的是仔细分析指导书,找出指导书中的一些边界情况

针对这些边界情况或者易忽视的情况,构造测试数据进行测试

随机测试就是随机构造数据,可以自己编写(或白嫖)测试程序

我的感受

本次作业,我学会了多线程的基础知识、对象并发及其协同方法、线程安全保护、设计模式等内容。

HW1要求较为简单,只涉及竖向的单电梯楼座,对于大体的架构,由于有实验课程的帮助,问题不大。

但多线程设计,需要考虑的线程安全性等问题,则是一道难关。如果考虑不周会经常出现“意想不到”,“难以复现”的bug。如轮询等错误,在本地测试上甚至难以体现,在交到评测机上才发现CPU时间爆炸。

本次作业,我被hack到了,其原因是没好好看指导书,指导书上是有提示输出包线程不安全的问题的,但当时由于对于线程安全重要性认识不够,没有太过注意,导致bug出现,需要记下这个教训。

第二次作业

作业简介

较为复杂的多线程实时电梯系统,新增楼座与楼座之间移动的环形电梯,可以有多部电梯,电梯可动态增加

StarUML图与类结构

其中,各个类的含义如下:

|- MainClass:主类
|- Schedule1:竖向楼座调度类
|- Schedule2:横向楼层调度类
|- Elevator1:竖向电梯类
|- Elevator2:横向电梯类
|- Queue:请求队列类
|- Input:输入处理类
|- Output:输出类

SequenceDiagram

架构分析

整体思路

本次作业,新增的需求有:

1、横向环形电梯

横向电梯其实本质上和竖向电梯区别不大,我类比竖向电梯进行横向电梯的设计。

我将横向电梯A->B方向类比为竖向电梯的向上,横向电梯A->E方向类比为竖向电梯的向下。竖向电梯向上楼层递增,向下楼层递减,而在横向电梯中也是如此,比如A->B,座号也是递增的,区别在于其是环形的,当E+1=F以后,需要特判一下,把F改为A(回到A座),A-1的情况同理改为E。

在竖向电梯中,请求自带方向属性(ToFloor与FromFloor大小关系),而在横向电梯中,请求也可以具有方向。参考指导书的方案,以最短路径方向作为指令的方向,比如A->B是“向上”,A->E是”向下“。

2、新增电梯

楼座(楼层)调度类里面有位于该楼座(楼层)的电梯list,以及每个电梯的请求等待list

private final ArrayList<Elevator1> elevators = new ArrayList<>();
private final ArrayList<Queue> queues = new ArrayList<>();

新增电梯,只需要分析请求,得出电梯应该加到哪个楼座或楼层,然后加入其调度类里面的list即可。

public void elevatorreq1(ElevatorRequest request) {
    switch (request.getBuilding()) {
        case 'A': {
            将电梯放入A座
            break;
        }
        case 'B': {
        	......
        }
        ......
     }
}

3、多部电梯

多部电梯带来的核心问题就是,对于一个请求,应该使用该楼座(楼层)的哪部电梯来接呢?

我采用的策略是请求分配策略,即请求进入时就为其分配一个电梯。

分配原则与大体框架如下:

for (遍历该楼座(楼层)的总请求队列) {
    if (如果有电梯为空,正在wait) {
        分配给空电梯。
        如果有多部空电梯,则分配给离得最近的空电梯
    }
    else if (有正在运行的电梯可稍带) {
        分配给可稍带电梯
        如果有多部可稍带,则分配给当前状态下请求数最少的电梯
    }
    else {
    	分配给当前状态下请求数最少的电梯
    }
}

同步块的设置和锁的选择

本次作业,共享资源有每个座电梯Elevator以及其对应的请求等待队列Queue、电梯调度器Schedule和Output。

对于同步块的设置和锁的选择,我采用了简单的synchronized关键字。

Output类,我设置打印方法println为同步方法:

public synchronized void println(String msg) {
    TimableOutput.println(msg);
}

对于等待队列,我在需要使用它们的地方,设置同步块:

synchronized (queue) {
	queue操作(加指令、删指令或者其他)
}

对于电梯调度器,我将其内部的方法,设为同步方法:

public synchronized void addRequest(PersonRequest p) {
    do something
}

对于电梯类,我将其被多线程调用的方法,设为同步方法:

public synchronized String getTheState() {
    do something
}

性能优化

1、实现捎带策略

2、多部电梯择优分配请求的策略

3、环形电梯按较短路径走,而不是一直一个方向

程序分析

度量分析

class OCavg OCmax WMC
Input 7.285714285714286 12.0 51.0
Elevator1 4.0 21.0 60.0
Elevator2 4.0 19.0 72.0
Schedule1 2.5454545454545454 6.0 28.0
Schedule2 2.3 6.0 23.0
MainClass 1.0 1.0 1.0
Output 1.0 1.0 3.0
Queue 1.0 1.0 4.0
Elevator1.State 0.0
Elevator2.State 0.0
Total 242.0
Average 3.5072463768115942 8.375 24.2

OCavg = Average opearation complexity(平均操作复杂度)

OCmax = Maximum operation complexity(最大操作复杂度)

WMC = Weighted method complexity(加权方法复杂度)

可以看到Input类、Elevator1类、Elevator2类复杂度较高。

输入处理类平均操作复杂度较高,可能与多重条件判断与switch有关,两个电梯类复杂度较高可能与循环层数较多,条件判断较多等原因有关。

其余类复杂度尚可。

方法复杂度分析

method CogC ev(G) iv(G) v(G)
Elevator1.dispatch() 56.0 1.0 12.0 25.0
Elevator2.dispatch() 56.0 1.0 10.0 23.0
Input.run() 19.0 3.0 8.0 8.0
Schedule1.run() 14.0 3.0 9.0 9.0
Schedule2.run() 14.0 3.0 9.0 9.0
Elevator1.parser(ArrayList, ArrayList) 10.0 1.0 7.0 7.0
Elevator2.parser(ArrayList, ArrayList) 10.0 1.0 7.0 7.0
Schedule1.takenable(PersonRequest) 9.0 3.0 5.0 7.0
Elevator2.isup(PersonRequest) 8.0 4.0 2.0 4.0
Elevator2.isupp(PersonRequest) 8.0 4.0 2.0 4.0
Elevator2.isuppp(PersonRequest) 8.0 4.0 2.0 4.0
Elevator1.stay() 7.0 1.0 8.0 8.0
Elevator2.stay() 7.0 1.0 8.0 8.0
Schedule1.available(PersonRequest) 7.0 1.0 4.0 5.0
Schedule2.available(PersonRequest) 7.0 1.0 4.0 5.0
Schedule1.setElevator(ArrayList) 5.0 4.0 4.0 4.0
Elevator1.in() 3.0 3.0 2.0 3.0
Elevator1.run() 3.0 3.0 9.0 10.0
Elevator2.in() 3.0 3.0 2.0 3.0
Elevator2.run() 3.0 3.0 9.0 10.0
Schedule1.latest(PersonRequest) 3.0 1.0 3.0 3.0
Schedule2.latest(PersonRequest) 3.0 1.0 3.0 3.0
Schedule2.setElevator(Vector) 3.0 3.0 3.0 3.0
Elevator1.getDirection() 2.0 3.0 1.0 3.0
Elevator1.getTheState() 2.0 3.0 1.0 3.0
Elevator2.down() 2.0 1.0 2.0 3.0
Elevator2.getDirection() 2.0 3.0 1.0 3.0
Elevator2.getTheState() 2.0 3.0 1.0 3.0
Elevator2.up() 2.0 1.0 2.0 3.0
Input.init() 2.0 1.0 3.0 3.0
Input.setend() 2.0 1.0 3.0 3.0
Elevator1.close() 1.0 1.0 2.0 2.0
Elevator1.down() 1.0 1.0 2.0 2.0
Elevator1.open() 1.0 1.0 2.0 2.0
Elevator1.out() 1.0 1.0 2.0 2.0
Elevator1.up() 1.0 1.0 2.0 2.0
Elevator2.close() 1.0 1.0 2.0 2.0
Elevator2.open() 1.0 1.0 2.0 2.0
Elevator2.out() 1.0 1.0 2.0 2.0
Input.elevatorreq1(ElevatorRequest) 1.0 1.0 6.0 6.0
Input.elevatorreq2(ElevatorRequest) 1.0 1.0 11.0 11.0
Input.personreq1(PersonRequest) 1.0 1.0 6.0 6.0
Input.personreq2(PersonRequest) 1.0 1.0 11.0 11.0
Schedule1.availElevatorIndex(PersonRequest) 1.0 2.0 1.0 2.0
Schedule2.availElevatorIndex(PersonRequest) 1.0 2.0 1.0 2.0
Elevator1.Elevator1(Queue, int, int, int, char, int, long, ...) 0.0 1.0 1.0 1.0
Elevator1.getNowFloor() 0.0 1.0 1.0 1.0
Elevator1.getPeople() 0.0 1.0 1.0 1.0
Elevator2.Elevator2(Queue, int, int, int, char, int, long, ...) 0.0 1.0 1.0 1.0
Elevator2.getCurBuilding() 0.0 1.0 1.0 1.0
Elevator2.getPeople() 0.0 1.0 1.0 1.0
MainClass.main(String[]) 0.0 1.0 1.0 1.0
Output.Output() 0.0 1.0 1.0 1.0
Output.getInstance() 0.0 1.0 1.0 1.0
Output.println(String) 0.0 1.0 1.0 1.0
Queue.Queue(boolean, ArrayList) 0.0 1.0 1.0 1.0
Queue.getQueueisEnd() 0.0 1.0 1.0 1.0
Queue.getWaitQueue() 0.0 1.0 1.0 1.0
Queue.setQueueisEnd(boolean) 0.0 1.0 1.0 1.0
Schedule1.Schedule1(int) 0.0 1.0 1.0 1.0
Schedule1.addElevator(int) 0.0 1.0 1.0 1.0
Schedule1.addRequest(PersonRequest) 0.0 1.0 1.0 1.0
Schedule1.dispacher() 0.0 1.0 1.0 1.0
Schedule1.setEnd(boolean) 0.0 1.0 1.0 1.0
Schedule2.Schedule2(int) 0.0 1.0 1.0 1.0
Schedule2.addElevator(int) 0.0 1.0 1.0 1.0
Schedule2.addRequest(PersonRequest) 0.0 1.0 1.0 1.0
Schedule2.dispacher() 0.0 1.0 1.0 1.0
Schedule2.setEnd(boolean) 0.0 1.0 1.0 1.0
Total 296.0 109.0 222.0 274.0
Average 4.2898550724637685 1.5797101449275361 3.217391304347826 3.971014492753623

CogC=Cognitive complexity`(认知复杂度)

ev(G) = Essential cyclomatic complexity(基本圈复杂度)

iv(G) = Design complexity(设计复杂度)

v(G) = cyclonmatic complexity(圈复杂度)

可以看到,dispatch方法复杂度较高。调度方法主要解决的问题是,请求从电梯请求等待队列进入电梯;捎带;电梯基于主请求的运行方向等问题,确实较为复杂,可能具体算法还有待改进。

其余方法复杂度尚可。

Bug分析

程序设计过程中的bug

程序设计过程中遇到过的bug,多数为细节处理的考虑不周造成的。

比如横向电梯的运行,曾出现横向电梯反复横跳的问题。

公测中的bug

HW2的公测中,我的程序没有被检测出bug。

互测中的bug

HW2的互测中,我的程序没有被检测出bug。

HW2的互测中,我未能发现房间中其他同学的bug。

测试思路

测试思路是 坑点针对测试+随机测试

其中坑点针对测试需要的是仔细分析指导书,找出指导书中的一些边界情况

针对这些边界情况或者易忽视的情况,构造测试数据进行测试

随机测试就是随机构造数据,可以自己编写(或白嫖)测试程序

我的感受

本次作业,我对于多线程的协调、线程安全及其设计等有了更深入的认识。

在实验课上,也学到了服务器架构的线程交互与流水架构的线程交互,虽然流水线架构并没有在此次作业中使用(因为没人走”斜线“),但也许下次作业就会出现需要换乘的情况了,我觉得可以类比worker的工序设计来完成。

这次作业,相较于第一次作业,新增了横向环形电梯、可以有多部电梯、可以新增电梯,看似变化很大。但仔细思考会发现其实横向电梯和竖向电梯在整体架构上,实际上区别不大,完全可以类比。因此此次作业并没有花费太长时间,我继续沿用了第一次作业的架构,扩充了多部电梯的请求分配策略,扩充了支持新增电梯的方法。

第三次作业

作业简介

复杂的多线程实时电梯系统横向电梯楼座停靠可配置,电梯容量和运行时间可配置,路径可达性问题(换乘

StarUML图与类结构

其中,各个类的含义如下:

|- MainClass:主类
|- Controller:主控制类
|- Counter:请求计数类
|- Schedule1:竖向楼座调度类
|- Schedule2:横向楼层调度类
|- Elevator1:竖向电梯类
|- Elevator2:横向电梯类
|- Queue:请求队列类
|- Req:请求类
|- Input:输入处理类
|- Output:输出类

SequenceDiagram

架构分析

整体思路

本次作业,功能上与HW2相比,有所增强。

1、横向电梯楼座停靠可配置,电梯容量和运行时间可配置

由于原先设计,电梯容量、运行时间等就可以配置,此处无需更改。

对于可停靠问题,横向电梯类新增成员mm,以掩码的形式保存可停靠信息:

private int mm;
public synchronized int getmm() {
        return mm;
}

新增getreachable方法判断是否可停靠:

public synchronized boolean getreachable(char building) {
    if ( buiding可停靠 ) {
        return true;
    }
    return false;
}

2、换乘处理

我的思路是,电梯个体的运营策略不变,通过对于请求的再封装来实现换乘,参考了实验课的流水架构。

请求再封装:建立Req类,Req类结构如下:

public class Req {
    private PersonRequest request;
    private int fromfloor;
    private int tofloor;
    private char frombuilding;
    private char tobuilding;

    public Req(PersonRequest request) {......}

    public void setfromfloor(int a) {......}

    public void settofloor(int a) {......}

    public void setfrombuilding(char c) {......}

    public void settobuilding(char c) {......}

    public int getPersonId() { return request.getPersonId(); }

    public int getFromFloor() { return fromfloor; }

    public int getToFloor() { return tofloor; }

    public char getFromBuilding() { return frombuilding; }

    public char getToBuilding() { return tobuilding; }

    public char gettrueToBuilding() {
        return request.getToBuilding();
    }

    public int gettrueToFloor() {
        return request.getToFloor();
    }
}

可以看到的是Req类实际上与请求类成员属性上没有区别,它的特点是,支持更改起始地点与目的地点

我的想法是,比如A-2 -> B-5的指令,现在我要采取A-2 -> A-1 -> B-1 -> B-5的方式,那么就先更改Req类的目标地点为A-1,然后当作正常的竖向请求放入A座电梯中。等到达了A-1以后,出电梯时判断是否到达最终目的地。

request.gettrueToBuilding()与request.gettrueToFloor() 返回最终目的地点。

如果到达了,就释放请求;如果没到达,以当前位置为起始地点,以最终目的为目标地点,设置Req,将其返回给主控制类进行再次分配。

public void out() {
    for (Req request:queueout) {
        ......  
        if (request.gettrueToBuilding() != id || request.gettrueToFloor() != nowfloor) {
            request.setfrombuilding(id);
            request.setfromfloor(nowfloor);
            request.settobuilding(request.gettrueToBuilding());
            request.settofloor(request.gettrueToFloor());
            Controller.getInstance().alloc(request);
        } else {
            Counter.getInstance().release();
        }
        ......
    }
}

Counter类:

这个类用来判断请求是否全部结束,很重要,如果没有这个请求判断会造成有指令还在换乘,但输入NULL,所有电梯停止(指令还没换乘结束)的bug。这个类的架构我完全采用了实验课的架构,不再赘述。

public class Counter {
    private int count;
    private static final Counter COUNTER = new Counter();

    Counter() {
        count = 0;
    }

    public static Counter getInstance() {
        return COUNTER;
    }

    public synchronized void release() { //代表完成一个任务
        ......
    }

    public synchronized void acquire() { //检验一个任务的完成,如果没有已完成的任务,等待
        ......
    }
}

换乘策略:

if (起始楼层存在横向电梯:可同时停靠起始楼座与最终楼座) {
	//横向运动,故设置目的楼层为起始楼层,目的楼座为最终楼座
    request.settofloor(request.getFromFloor());
    request.settobuilding(request.getToBuilding());
    将请求传入符合要求的横向电梯
    return;
}
if (目标楼层存在横向电梯:可同时停靠起始楼座与最终楼座) {
	//需要先到达目标楼层,故竖向运动,设置目的楼层为最终楼层,目的楼座为起始楼座
    request.settofloor(request.getToFloor());
    request.settobuilding(request.getFromBuilding());
    将请求传入符合要求的竖向电梯
    return;
}
if (某楼层存在横向电梯:可同时停靠起始楼座与最终楼座) {
	如果存在多个楼层符合情况,则选取其中离起始楼层与最终楼层距离和最小的楼层
	//需要先到达这一楼层,故竖向运动,设置目的楼层为这一楼层,目的楼座为起始楼座
	request.settofloor(符合条件横向电梯所在楼层);
	request.settobuilding(request.getFromBuilding());
    将请求传入符合要求的竖向电梯
    return;
}

同步块的设置和锁的选择

本次作业,共享资源有每个座的电梯,以及其对应的请求等待队列(被input和对应电梯,两个线程访问),电梯调度器类,Output,Controller,Counter。

对于同步块的设置和锁的选择,我采用了简单的synchronized关键字。

Output类,我设置打印方法println为同步方法:

public synchronized void println(String msg) {
    TimableOutput.println(msg);
}

对于等待队列,我在需要使用它们的地方,设置同步块:

synchronized (queue) {
	queue操作(加指令、删指令或者其他)
}

对于电梯调度器,我将其内部的方法,设为同步方法:

public synchronized void addRequest(PersonRequest p) {
    do something
}

对于电梯类,我将其被多线程调用的方法,设为同步方法:

public synchronized String getTheState() {
    do something
}

对于主控类,我设置同步块:

synchronized (this) {
	do something
}

对于Counter类,我设置同步块:

public synchronized void release() { //代表完成一个任务
    do something
}

性能优化

1、实现捎带策略。

2、支持选择最近距离的横向电梯换乘

3、支持横向电梯的二次换乘

4、多部电梯择优分配请求的策略

5、环形电梯按较短路径走,而不是一直一个方向

程序分析

度量分析

class OCavg OCmax WMC
Input 7.0 7.0 7.0
Controller 5.375 12.0 43.0
Elevator1 3.7058823529411766 21.0 63.0
Elevator2 3.6666666666666665 19.0 77.0
Schedule2 3.0 9.0 24.0
Schedule1 2.5454545454545454 6.0 28.0
Counter 1.5 3.0 6.0
MainClass 1.0 1.0 1.0
Output 1.0 1.0 3.0
Queue 1.0 1.0 4.0
Req 1.0 1.0 12.0
Elevator1.State 0.0
Elevator2.State 0.0
Total 268.0
Average 2.977777777777778 7.363636363636363 20.615384615384617

OCavg = Average opearation complexity(平均操作复杂度)

OCmax = Maximum operation complexity(最大操作复杂度)

WMC = Weighted method complexity(加权方法复杂度)

可以看到Input类、Controller类、Elevator类复杂度较高。

可能原因是主控Controller类的电梯换乘算法复杂度高,Elevator电梯运行策略复杂度较高。

其余类复杂度尚可。

方法复杂度分析

method CogC ev(G) iv(G) v(G)
Elevator1.dispatch() 58.0 1.0 12.0 27.0
Elevator2.dispatch() 58.0 1.0 10.0 25.0
Schedule2.gettwoscale(char, char) 37.0 1.0 9.0 10.0
Input.run() 17.0 3.0 8.0 8.0
Schedule1.run() 14.0 3.0 9.0 9.0
Schedule2.run() 11.0 3.0 6.0 6.0
Controller.personreq2(Req) 10.0 5.0 8.0 8.0
Elevator1.parser(ArrayList, ArrayList) 10.0 1.0 7.0 7.0
Elevator2.parser(ArrayList, ArrayList) 10.0 1.0 7.0 7.0
Schedule1.takenable(Req) 9.0 3.0 5.0 7.0
Elevator2.isup(Req) 8.0 4.0 2.0 4.0
Elevator2.isupp(Req) 8.0 4.0 2.0 4.0
Elevator2.isuppp(Req) 8.0 4.0 2.0 4.0
Counter.acquire() 7.0 3.0 4.0 4.0
Elevator1.stay() 7.0 1.0 8.0 8.0
Elevator2.stay() 7.0 1.0 8.0 8.0
Schedule1.available(Req) 7.0 1.0 4.0 5.0
Schedule2.getscale(char, char) 7.0 1.0 4.0 5.0
Elevator1.out() 5.0 1.0 4.0 4.0
Elevator2.out() 5.0 1.0 4.0 4.0
Schedule1.setElevator(ArrayList) 5.0 4.0 4.0 4.0
Elevator1.in() 3.0 3.0 2.0 3.0
Elevator1.run() 3.0 3.0 9.0 10.0
Elevator2.in() 3.0 3.0 2.0 3.0
Elevator2.run() 3.0 3.0 9.0 10.0
Schedule1.latest(Req) 3.0 1.0 2.0 3.0
Controller.alloc(Req) 2.0 1.0 2.0 2.0
Controller.init() 2.0 1.0 3.0 3.0
Controller.setend() 2.0 1.0 3.0 3.0
Elevator1.getDirection() 2.0 3.0 1.0 3.0
Elevator1.getTheState() 2.0 3.0 1.0 3.0
Elevator2.down() 2.0 1.0 2.0 3.0
Elevator2.getDirection() 2.0 3.0 1.0 3.0
Elevator2.getTheState() 2.0 3.0 1.0 3.0
Elevator2.up() 2.0 1.0 2.0 3.0
Controller.elevatorreq1(ElevatorRequest) 1.0 1.0 6.0 6.0
Controller.elevatorreq2(ElevatorRequest) 1.0 1.0 11.0 11.0
Controller.personreq1(Req) 1.0 1.0 6.0 6.0
Elevator1.close() 1.0 1.0 2.0 2.0
Elevator1.down() 1.0 1.0 2.0 2.0
Elevator1.open() 1.0 1.0 2.0 2.0
Elevator1.up() 1.0 1.0 2.0 2.0
Elevator2.close() 1.0 1.0 2.0 2.0
Elevator2.getreachable(char) 1.0 2.0 1.0 2.0
Elevator2.open() 1.0 1.0 2.0 2.0
Schedule1.availElevatorIndex(Req) 1.0 2.0 1.0 2.0
Schedule2.Schedule2(int) 1.0 1.0 2.0 2.0
Controller.getInstance() 0.0 1.0 1.0 1.0
Counter.Counter() 0.0 1.0 1.0 1.0
Counter.getInstance() 0.0 1.0 1.0 1.0
Counter.release() 0.0 1.0 1.0 1.0
Elevator1.Elevator1(Queue, int, int, int, char, int, long, ...) 0.0 1.0 1.0 1.0
Elevator1.getNowFloor() 0.0 1.0 1.0 1.0
Elevator1.getPeople() 0.0 1.0 1.0 1.0
Elevator1.getSize() 0.0 1.0 1.0 1.0
Elevator1.getSpeed() 0.0 1.0 1.0 1.0
Elevator2.Elevator2(Queue, int, int, int, char, int, long, ...) 0.0 1.0 1.0 1.0
Elevator2.getCurBuilding() 0.0 1.0 1.0 1.0
Elevator2.getPeople() 0.0 1.0 1.0 1.0
Elevator2.getSpeed() 0.0 1.0 1.0 1.0
Elevator2.getmm() 0.0 1.0 1.0 1.0
MainClass.main(String[]) 0.0 1.0 1.0 1.0
Output.Output() 0.0 1.0 1.0 1.0
Output.getInstance() 0.0 1.0 1.0 1.0
Output.println(String) 0.0 1.0 1.0 1.0
Queue.Queue(boolean, ArrayList) 0.0 1.0 1.0 1.0
Queue.getQueueisEnd() 0.0 1.0 1.0 1.0
Queue.getWaitQueue() 0.0 1.0 1.0 1.0
Queue.setQueueisEnd(boolean) 0.0 1.0 1.0 1.0
Req.Req(PersonRequest) 0.0 1.0 1.0 1.0
Req.getFromBuilding() 0.0 1.0 1.0 1.0
Req.getFromFloor() 0.0 1.0 1.0 1.0
Req.getPersonId() 0.0 1.0 1.0 1.0
Req.getToBuilding() 0.0 1.0 1.0 1.0
Req.getToFloor() 0.0 1.0 1.0 1.0
Req.gettrueToBuilding() 0.0 1.0 1.0 1.0
Req.gettrueToFloor() 0.0 1.0 1.0 1.0
Req.setfrombuilding(char) 0.0 1.0 1.0 1.0
Req.setfromfloor(int) 0.0 1.0 1.0 1.0
Req.settobuilding(char) 0.0 1.0 1.0 1.0
Req.settofloor(int) 0.0 1.0 1.0 1.0
Schedule1.Schedule1(int) 0.0 1.0 1.0 1.0
Schedule1.addElevator(int, double, int) 0.0 1.0 1.0 1.0
Schedule1.addRequest(Req) 0.0 1.0 1.0 1.0
Schedule1.dispacher() 0.0 1.0 1.0 1.0
Schedule1.setEnd(boolean) 0.0 1.0 1.0 1.0
Schedule2.addElevator(int, double, int, int) 0.0 1.0 1.0 1.0
Schedule2.addRequest(Req, int) 0.0 1.0 1.0 1.0
Schedule2.getdistance(int, int) 0.0 1.0 1.0 1.0
Schedule2.setEnd(boolean) 0.0 1.0 1.0 1.0
Total 353.0 134.0 249.0 307.0
Average 3.922222222222222 1.488888888888889 2.7666666666666666 3.411111111111111

CogC=Cognitive complexity`(认知复杂度)

ev(G) = Essential cyclomatic complexity(基本圈复杂度)

iv(G) = Design complexity(设计复杂度)

v(G) = cyclonmatic complexity(圈复杂度)

可以看到,dispatch方法、gettwoscale方法复杂度较高。

可能原因是电梯调度算法较复杂,横向电梯换乘策略算法较复杂。

其余方法复杂度尚可。

Bug分析

程序设计过程中的bug

程序设计过程中遇到过的bug,多数为细节处理的考虑不周造成的。

比如曾出现有指令还在换乘,但输入NULL,所有电梯停止(指令还没换乘结束)的bug,其原因是对于线程结束的判断出了问题。我参考实验课,引入了Counter类解决了这个问题。

公测中的bug

HW3的公测中,我的程序没有被检测出bug。

互测中的bug

HW3的互测中,我的程序被检测出了2组bug,均为同质bug。bug原因是:当添加电梯指令和乘客请求同时到来时,如果此电梯恰好为该乘客换乘的最佳选择电梯,程序会选择该电梯,但该电梯并未准备好,会出现请求无法执行的bug。

HW3的互测中,我没有hack到其他同学。

测试思路

测试思路是 坑点针对测试+随机测试

其中坑点针对测试需要的是仔细分析指导书,找出指导书中的一些边界情况

针对这些边界情况或者易忽视的情况,构造测试数据进行测试。

随机测试就是随机构造数据,可以自己编写(或白嫖)测试程序。

我的感受

本次作业,我实践了流水设计模式,加深了对于多线程协调与线程安全的理解。

本次作业,看似只是多了可达性的问题,但由于其复杂性,整个电梯系统的设计难度都大大增加了,换乘所带来的请求多次出入电梯,也带来了许多线程安全隐患与程序设计障碍。其中一些不易察觉的bug,更是在课下自查中没有查出,而在互测中被暴露了出来。

对第二单元的感受

第二单元通过3次迭代开发的作业,让我体会到了多线程程序设计的思想特质、让我学会了多线程的基础知识和编程方法,熟悉了同步块与锁、线程安全设计、多线程设计模式等内容。

作业难度不小、颇具挑战,多线程的不确定性、一些bug的难以复现性更是为我带来了不小的困难。在线程安全设计,并发及其协调设计、并发场景的需求分析与设计等方面我还需要继续加强。

3次作业,我在公测中没有被查出任何bug,在互测中HW1被查出1个bug,HW3被查出1个bug。总体而言还是满意的,bug较少也证明了我在课下程序设计与测试中的付出没有白费。

今后课程的挑战会更多,我期望学习更多,收获更多。

posted @   ^Sternstunde  阅读(85)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示