OO第5-7次做业总结

OO第5-7次做业总结

这三次做业所有是关于电梯的。主要锻炼了多线程编程的能力,以及了解一些调度算法的使用。html

[TOC]java

一、设计分析

1.一、单电梯FCFS调度方案

第五次做业,不是通常的简单,几乎人人满分的那种。dalao们甚至在30行内写出单线程调度就解决了。摸鱼划水的一次。本着练习多线程的目的,我仍是老老实实写了调度。只有一个请求队列,因为不考虑捎带,因此直接FIFO队列便可。因为当时对同步和锁的掌握很浅,为了求稳,对于同步访问(队列为空时等待)采用了轮询的办法。这样虽避免了死锁,但浪费CPU时间。正则表达式

架构:首先定义两个线程(输入、电梯),它们在主线程中启动,共享一个请求队列。这队列用线程安全容器LinkedBlockingQueue。一方面是为了阻塞,另外一方面是为了保证队列长度(即size()方法)的访问是一个原子操做。另外定义一个状态类WhenToStop,用来指示输入是否结束。stopInputting()方法用来通知两个线程输入已结束。算法

电梯的移动,是直接到目标楼层的,不须要考虑某一层掉头等的问题。编程

UML类图安全

image

复杂度分析多线程

Methods:架构

Method ev(G) iv(G) v(G)
Elevator::Constructor 1 1 1
Elevator.absSub(int,int) 2 1 2
Elevator.closeDoor() 1 3 3
Elevator.getOff() 1 1 1
Elevator.getOn() 1 1 1
Elevator.openDoor() 1 3 3
Elevator.run() 2 3 4
Elevator.toFloor(int) 1 3 3
InputThread::Constructor 1 1 1
InputThread.run() 1 4 4
Main.main(String[]) 1 1 1
WhenToStop.isInputting() 1 1 1
WhenToStop.stopInputting() 1 1 1
Total 15.0 24.0 26.0
Average 1.15 1.85 2.0

Classes:并发

Class OCavg WMC
Elevator 1.625 13.0
InputThread 1.5 3.0
Main 1.0 1.0
WhenToStop 1.0 2.0
Total 19.0
Average 1.46 4.75

耦合度分析ide

Class Cyclic Dcy Dcy* Dpt Dpt*
Elevator 0 1 1 1 1
InputThread 0 1 1 1 1
Main 0 3 3 0 0
WhenToStop 0 0 0 3 3
Average 0.0 1.25 1.25 1.25 1.25

复杂度控制仍是比较好的,没有一个方法的复杂度超过5。耦合度也不高。线程间通讯只靠两个共享对象。

时序图(线程间通讯机制)

image

1.二、单电梯可捎带

此次须要可捎带,因此第五次做业那种只设置一个共享队列的方法不适用了。由于捎带请求必须同时考虑电梯外请求和电梯内乘客,并在合适时机掉头。

我查了一下可行的调度算法,包括:scan算法,look算法,ALS,还有FCFS,最近距离等

scan算法是上下循环调度,相似于摆渡车、轮渡,缺点是没有乘客的楼层也要停。

look是scan的改进,没有乘客和请求的楼层不停,直接掉头。

ALS和FCFS太熟悉了,不用说。

还有一种是最近距离,也就是当电梯内有乘客时,乘客优先。无乘客时,距离当前楼层最近的请求优先。但这会出现某些请求“饿死”的状况。

综合以上几种,仍是look比较好。

基本思想就是,请求要分为电梯外请求和电梯内乘客两部分。电梯外请求按照出发楼层分类,电梯内请求按照目标楼层分类。每当电梯到达某一层时,检查是否须要上客或下客。下客的标准是到达目标楼层,上客的标准是他的请求方向和电梯当前运行方向(不是主请求的请求方向)相同。有则开门,无则甩过直接走。每当准备移动到下一层时,检查是否须要掉头(检查是否掉头是一个扫描过程,若是电梯内有乘客则不能掉头,不然扫描电梯外请求,若是当前方向上没有请求则掉头,若是两边都没有请求,则停下来等待)。

此次因为是单部电梯,因此直接复用上次的架构,不须要增长调度盘。线程安全上,考虑了用lockcodition来进行同步、互斥。这样灵活性比较高。

所用容器以下(直接粘贴Main类源码,省略import):

public class Main {
    public static void main(String[] args) {
        TimableOutput.initStartTimestamp();

        final ReentrantLock reentrantLock = new ReentrantLock(); // 锁和条件
        final Condition condition = reentrantLock.newCondition();

        final HashMap<Integer, LinkedBlockingQueue<PersonRequest>> outerRequests
                = new HashMap<>(); // 电梯外请求
        for (int i = -2; i <= 16; i++) { // 这里的楼层作了特殊处理,以便电梯能连续运行,输出时再转换
            outerRequests.put(i, new LinkedBlockingQueue<>());
        } // 每层的请求都要初始化一个空队列
        final InputtingState state = new InputtingState(); // 指示输入是否完成

        InputThread inputThread = new InputThread(outerRequests,
                reentrantLock, condition, state); // 输入线程
        Elevator elevator = new Elevator(outerRequests,
                reentrantLock, condition, state); // 电梯线程

        elevator.start();
        inputThread.start();
    }
}

电梯内乘客的容器只在Elevator类中定义和使用,不须要共享。容器类型和电梯外请求相同。

UML类图

image

复杂度分析

Methods

Method ev(G) iv(G) v(G)
Elevator::Constructor 1 2 2
Elevator.checkDirection() 10 6 10
Elevator.closeDoor() 1 4 4
Elevator.downOneFloor() 1 3 3
Elevator.getFloor() 2 1 2
Elevator.getOff() 1 2 2
Elevator.getOn() 3 5 5
Elevator.isEmpty(HashMap<Integer, LinkedBlockingQueue<PersonRequest>>) 3 2 3
Elevator.moveOneFloor() 1 2 2
Elevator.openDoor() 1 3 3
Elevator.realFloor(int) 2 1 2
Elevator.run() 3 9 10
Elevator.upOneFloor() 1 3 3
InputThread::Constructor 1 1 1
InputThread.run() 1 4 4
InputtingState.isInputting() 1 1 1
InputtingState.stopInputting() 1 1 1
Main.main(String[]) 1 2 2
Total 35.0 52.0 60.0
Average 1.94 2.89 3.33

Classes

Class OCavg WMC
Elevator 3.08 40
InputThread 1.5 3
InputtingState 1 2
Main 2 2
Total 47.0
Average 2.61 11.75

可见检查掉头的方法checkDirection()的复杂度仍是比较高的,远远超过平均复杂度。这个方法要综合考虑乘客和外请求,代码量也比较大,达到了30行。另外,Elevator类是实现核心功能的,可是包含了调度方法,因此复杂度也比较高。当时是为了方便,调度器和请求队列合一。

耦合度分析

Class Cyclic Dcy Dcy* Dpt Dpt*
Elevator 0 1 1 2 2
InputThread 0 2 2 1 1
InputtingState 0 0 0 3 3
Main 0 3 3 0 0
Average 0.0 1.5 1.5 1.5 1.5

耦合度依然不高,由于两个线程通讯仍然只须要两个共享对象、一把锁、一个条件(监听器)。

时序图(线程间通讯机制)

image

1.三、三部电梯协同调度

此次又不同了,不只增长了两部电梯,并且增长了乘客数量限制、停靠层限制。因此,这决定咱们须要一个调度盘来把乘客请求分配到合适的电梯上去。而且,考虑换乘问题。

电梯类统一建模,可是增长一些属性,包括停靠层限制、核载、运行速度。利用(半)工厂模式,构造函数只传入电梯类型,根据电梯类型来决定这些属性的值。因为仍然采用look算法,因此能够复用上次的电梯,只须要对电梯运动状况和掉头的条件作一些调整。

架构就是三个电梯、一个调度盘,请求队列分三块,一块是原始请求,也就是刚输入的时候,没有通过调度器分配,能够理解为站在大厅门外。一块是分配后请求,能够理解为站在某个电梯门口。最后是电梯内乘客。

换乘不难解决,为了一劳永逸的防止出错,咱们把未完成的请求扔回原始请求中,也就是回滚。这样保证各个电梯互相独立。电梯只负责运送乘客,无论任何调度问题。这样保证耦合度低,不易出错。

另外的坑点,注意中止条件。这一次,使用共享对象unfinishNum(自定义一个类SyncInteger,模拟原子整数),表示未完成的请求个数(从一个请求产生到它从电梯中出去并上到目标楼层为止,这段时间称做未完成)。当输入结束,而且未完成请求个数为0时,全部线程所有终止。再有就是注意输出互斥问题,由于输出函数对stdout是竞争关系,因此同一时刻只能有一个线程在输出,不然会致使输出穿插(这个穿插是指行内穿插)混乱。

三部电梯的调度,应该让它们尽可能并行,这一步由调度器Dispatcher来完成。调度器也是一个线程,和输入线程、电梯线程并发。具体的调度优化,详见:https://www.cnblogs.com/wancong3/p/10739633.html

UML类图

image

复杂度分析

Methods

Method ev(G) iv(G) v(G)
Dispatcher::Constructor 1 1 1
Dispatcher.dispatch(PersonRequest,int) 3 4 4
Dispatcher.fixedFloor(int,int,int) 13 4 15
Dispatcher.run() 3 11 12
Elevator::Constructor 2 7 10
Elevator.checkDirection() 10 9 13
Elevator.closeDoor() 1 4 4
Elevator.downOneFloor() 1 3 3
Elevator.fixedFloor(PersonRequest) 12 4 14
Elevator.getFloor() 2 1 2
Elevator.getOff() 1 4 4
Elevator.getOn() 5 6 10
Elevator.isEmpty(HashMap<Integer, LinkedBlockingQueue<PersonRequest>>) 3 3 4
Elevator.openDoor() 1 3 3
Elevator.realFloor(int) 2 1 2
Elevator.run() 3 11 12
Elevator.syncOutput(String) 1 1 1
Elevator.toNextFloor() 1 3 3
Elevator.upOneFloor() 1 3 3
InputThread::Constructor 1 1 1
InputThread.run() 1 4 4
Main.main(String[]) 1 7 7
SyncInteger.SyncInteger(int) 1 1 1
SyncInteger.getValue(int) 1 1 3
ThreadRunning.isRunning() 1 1 1
ThreadRunning.stopRunning() 1 1 1
Total 73.0 99.0 138.0
Average 2.81 3.81 5.31

Classes

Class OCavg WMC
Dispatcher 5.75 23
Elevator 4.33 65
InputThread 1.5 3
Main 7 7
SyncInteger 2 4
ThreadRunning 1 2
Total 104.0
Average 4.0 17.33

复杂度较高的方法就是fixedFloor(),用于寻找合适的换乘点。它采起了随机顺序的方法,而且循环搜索。

另外值得注意的就是,调度器类和电梯类的复杂度都比较高,多是由于使用了过多的共享对象。还有就是Main类的OCavg居然达到了7,也和初始化过多的共享对象有关。可是没有办法啊,原本电梯换乘是乘客考虑的,强行扔给调度器也是真的懒(哈哈哈~)。

耦合度分析

Class Cyclic Dcy Dcy* Dpt Dpt*
Dispatcher 0 3 3 1 1
Elevator 0 2 2 3 3
InputThread 0 3 3 1 1
Main 0 5 5 0 0
SyncInteger 0 0 0 4 4
ThreadRunning 0 0 0 4 4
Average 0.0 2.167 2.167 2.167 2.167

遵循SOLID原则,调度器和电梯功能分离,下降耦合度仍是有效果的,我以为不要管那些极限性能,这样设计才是最佳方案。系统稳定性比单个数据的性能重要得多。

SOLID原则

一、单一责任:每一个类只管本身该管的事情。我以为这个是重中之重,电梯就是运输乘客,调度器才负责具体哪一个乘客上哪一个电梯。这样增长了可维护性,即便要改电梯也不会牵一发动全身。

二、开闭控制:哪些类支持扩展,哪些类是final的,哪些函数设置为对外接口,哪些函数是封装的。另外,一样的功能能够定义成抽象方法,支持不一样的实现。电梯其实能够这样作,把开关门、上下客和移动到下一层的方法做为抽象方法。只不过,一次做业中没有多种不一样实现的电梯,因此目前还不必这么作。

三、里氏替换:和自定义类的继承(is-a继承)有关。extends Thread不是啥设计层面的继承,不用管。

四、依赖倒置:强调依赖关系中,高层模块的抽象。电梯固然不会依赖本身。(但这个原则在第三次做业,就是多项式、三角函数复合求导中相当重要,由于求导方法依赖于具体的表达式形式,只有把表达式类抽象出来才能获得语法树)

五、接口分离:说的是,不要用单一接口实现多功能。电梯的设计,远没有这么复杂。

时序图(线程间通讯机制)

image

二、bug杀虫和测试方法

若是按照测试的标准,这三次的强测没有错误。互测呢,根本没有针对性,很难发现bug。

来讲说中测发现的bug。第五次就不说了,没有bug。

第六次,没提交的时候,就有严重的问题,停不下来。后来发现使用await()signalAll()用错了位置,在lock()unlock()以外使用,不只没法结束,还报一大堆异常。

第七次更好笑。我知道三楼确定会出一些小差错,就故意输入三楼的数据,结果上下往返停不下来。发现是换乘点有问题。再后来本身手动输入没问题,连文件输入都没测试,过于自信的提交,结果等了半分钟不出结果,已经有点慌了。第四分钟,不出所料,通通通通RTLE。

好吧,个人数据果真不行。借了它的两组数据发现问题很严重,一部电梯停下时另两部没停。干脆这样吧,当一个电梯将要结束时,通知其它电梯也停下来(由于此时电梯中止的条件已经知足,只要一接到signal马上中止)。

有点怕了不敢乱提交,就本身用C写了一个数据生成器,又把官方的输入接口反编译了一下,本身加了定时输入。测了二三十组吧,没问题才敢提交(我知道复用上次的电梯,逻辑确定不会有问题,就怕出在同步问题上)。

互测呢,感受就是象征性地跑一跑交空刀刷活跃度。后来第六次身份公开,发现我和六系四大神仙一个组(具体是谁,我屋的人确定知道),我一打擦边球进A组的菜鸡也能享有这样的待遇,难怪谁都发现不了问题。专门拜读了他们的代码,感受一些强优化仍是很是牛B的,我等算法小白甘拜下风。另外,每一个人的调度思路都不一样,不太可能经过读代码来针对性出数据,只能是测一测比较容易出错的地方,而后随机生成几个大量数据走压力测试。和第一单元不同啊,第一单元彻底是语法分析,这是多线程。没有了WF写起来真轻松,互测就八脸懵逼了。

在这里特别感谢老柴削面、东方削面HDL、DYJ、ZYY三位奆佬提供的定时输入方法、Special Judge和对拍器,虽然最终没用它们发现bug,可是至少解了燃眉之急。

多线程调试,尤为是当死锁的时候,最好的方法就是printf,你能够在printf的字符串中加一个debug,记得在提交的时候逐一删除(能够用idea的正则表达式替换,超级方便,因此我说你写个debug这样的标志)。线程安全相关问题,printf大法好!

此次测试和第一单元不一样的地方在于,不是简单工具能够解决的,必须靠本身分析题目逻辑来判断是否正确。

三、收获

三次做业强迫我学习了多线程。其实最重要的收获就这一点。具体点就是线程安全考虑。只要是设计并发程序,时刻都要考虑线程安全。掌握了线程安全的几种方法:同步、条件锁、原子类、线程安全容器。

还有就是随机算法的认识,在数据良莠不齐或彻底随机的状况下,随机算法能显著提升平均性能。由于在实际中,最坏状况因为出现几率极小,每每不重要,这也是平摊分析的基本思想。

这三次做业,我感到OO通过改革比往届好得多了,除了前两次做业之外,已是真正的面向对象。类设计、多线程、并行编程在工程开发中仍是很是重要的。另外就是强测作得愈来愈好,互测已经不怎么是得分手段了,从而能真正体现出程序的质量(固然,有时候也会栽,强测炸的状况不是没有过,若是bug修复能捡回强测部分分就更好了)。

四、工具推荐

时序图工具:PlantUML Integration

安装方法:(这个是IDEA插件,喜欢用Eclipse的小伙伴自行去找,反正我是没找到)

一、IDEA-文件-设置

二、选择Plugins选项卡,单击上方Plugins Market

三、搜索PlantUML Integration

四、install

五、重启IDEA

使用方法详见:http://www.javashuo.com/article/p-uyqzdobb-ga.html

另外给你们推荐一个PlantUML详解:https://www.cnblogs.com/Jeson2016/p/6837186.html

相关文章
相关标签/搜索