2020北航OO第二单元总结

前言

本单元考察基于多线程的电梯调度问题,成功让我从一个多线程小白到了基本掌握了使用锁来控制线程安全的能力,收获颇多(充分体验了迷茫地de一个又一个死锁bug的痛苦)。

三次作业的关键如下:

第一次作业:单台电梯的调度,电梯可到达所有楼层,容量不设限,考虑捎带。

第二次作业:多台电梯的调度,通过输入控制电梯台数,电梯可到达所有楼层,容量受限,考虑捎带。

第三次作业:3+n台电梯的调度,通过输入随时增加电梯,电梯到达楼层、容量、运行时间分类受限,考虑换乘和捎带。

一、设计策略分析

本单元的电梯设计,我主要是利用了生产者消费者模式,将电梯需求作为生产者,电梯运载乘客作为消费者,将调度器作为托盘,处理需求并反馈当前需求信息,并将调度器设为单例模式,作为共享对象,供所有类访问。

对于请求队列的设计,我将总的请求队列存在调度器中,分为上行队列和下行队列,并为电梯设计自己的内部队列,存储已经进入电梯的需求。电梯在输入结束,并且所有队列均为空时,结束电梯线程。

总的工作模式,是采用输入将需求加入调度器中的请求队列,电梯根据调度器所返回的请求信息,主动向调度器申请请求的方式。

第一次作业:我采用了主线程和电梯线程的双线程模式,将输入放在主线程中,在主线程中进行电梯结束的判断,在电梯类中设置特定结束方法。

第二次作业:我为输入设计了一个单独的线程,每部电梯单独拥有一个线程,在主线程中创建输入和电梯线程,在调度器中设置静态变量的输入结束标志,仍在主线程中判断并结束电梯线程。

第三次作业:在上一次的基础上,将ABC三部电梯的创建放在主线程中,将临时电梯的创建放在输入线程中,输入线程将新增电梯返回给主线程,仍在主线程中结束电梯线程。 对于换乘需求,由于本次作业的换乘需求都可以通过一次换乘达到,我在调度器中新建了换乘队列,在有电梯申请请求时判断是否可通过此电梯进行换乘,并为此请求设置换乘层,在从换乘层出来后,将换乘请求加入主请求队列中。

二、扩展性分析

吸取了上一单元的教训,在本单元三次作业的过程中,由于我在设计之初便考虑着电梯的扩展性,而且后续作业的方向也比较好推测,我并没有进行重构,只是每次都加了一些属性或方法来实现新的要求。

代码行数变化:254→445→796

SOLID   My Project
SRP 单一责任原则 调度器进行需求调度,输入只负责获得请求,电梯只负责电梯的运行即出入,基本符合
OCP 开放封闭原则 三次作业的递进过程中,为了方便,加以习惯因素,都是采取在原有类中增加新方法的方式,没有很好地贯彻此原则
LSP 里氏替换原则 第三次作业对电梯类进行了扩展,没有违反此原则
ISP 接口隔离原则 没有实现接口
DIP 依赖倒置原则 所有类都依赖调度器类

三、程序结构分析

由于本单元的结构在第二次作业并没有改变,故直接贴出最后两次作业的项目UML类图。

在第三次作业中,由于将电梯分为ABC三类,故将原电梯扩展了三个子类,在每个子类中分别设置其特有属性(容量、可停靠层等),并新建了Person类。之前的两次作业一直在采用嵌套的ArrayList来储存需求信息,但第三次作业需求的属性更多了,同时也为了满足老师上课所讲的显式表达原则,故改变了原来的表示方式,这个的改动非常简单,我也可以明显感受到了此改变所带来的操作的方便性。

至于线程之间的协作关系,画出来的简单UML协作图如下

关于各个方法的复杂度分析如下(略去了一些简单的get和set方法),可以看出,大部分的方法复杂度都不是很高,只有调度器中的几个方法有较高的耦合度和复杂度,这可能归咎于优化时增加的一些判断方法来减少电梯的开关门次数,目前没有太好的解决办法

Method ev(G) iv(G) v(G)
Controller.Controller() 1 1 1
Controller.add(PersonRequest) 2 3 3
Controller.addTransQueue(Person,int,char) 2 3 4
Controller.addTransferPerson(Person) 1 2 2
Controller.canInDirection(char,Person) 3 2 3
Controller.canInFloor(char,int,Person) 1 2 2
Controller.canTransferInFloor(int,char,int,Person) 3 3 3
Controller.getDirectArrive(int,int) 4 1 4
Controller.getInstance() 1 1 3
Controller.getPeopleIn(int,int,char,int) 10 8 12
Controller.getTansToFloor(char) 4 3 4
Controller.getTransferFloor(Person,char) 8 4 8
Controller.havePeople(int,int,char) 8 6 8
Controller.havePeopleIn(int,int,char) 7 6 8
Controller.isEmpty() 1 4 4
Elevator.Elevator(String) 1 1 1
Elevator.arrive() 1 1 1
Elevator.changeDirection() 4 4 7
Elevator.closeDoor() 1 1 1
Elevator.elevatorRun() 2 1 3
Elevator.end() 1 2 3
Elevator.getPeopleSize() 2 2 3
Elevator.isEmpty() 1 2 2
Elevator.isNeedOpen() 4 5 8
Elevator.isNeedWait() 9 7 14
Elevator.open() 1 2 2
Elevator.openDoor() 1 1 1
Elevator.peopleIn() 1 5 5
Elevator.peopleOut() 1 4 4
Elevator.run() 4 6 9
Elevator.waitWorking(Integer) 1 1 2
ElevatorA.ElevatorA(String) 1 1 1
ElevatorB.ElevatorB(String) 1 1 1
ElevatorC.ElevatorC(String) 1 1 1
Input.Input() 1 1 1
Input.addElevator(ElevatorRequest) 1 2 4
Input.getElevators() 1 1 1
Input.run() 3 5 6
Main.main(String[]) 1 4 4
Person.Person(int,int,int) 1 1 1
Person.equals(Object) 3 1 8

四、bug及互测分析

第一次作业比较简单,顺利通过了所有强测点,也没被别人hack到,只是可能由于我只会让与电梯运行方向相同的人上电梯,也是考虑到以后可能会限制电梯容量,性能分不是特别高,现在想想第一次还是应该让所有人都上电梯,后面再改。

第二次作业,由于我测试不充分,在优化会有多部电梯对同一请求开门时,我采取了判断电梯是否在当前层开门时,提前将请求取进电梯的预约序列的方式,但由于我在过程中有多次判断,导致了后来的请求会把之前的请求覆盖的bug,因为这个bug,我成了全屋唯一被hack的人。。。虽然bug很好解决,但造成的损失也是巨大的,给我敲响了警钟。还有就是出现概率很低的死锁问题,我被仅有一条请求的输入hack到了,但是本地并无法复现,在bug修复时也直接通过了,所以我并没有深究,可能这也导致我在第三次作业的bug。

第三次作业,我被hack到了多个RTLE的问题,就是死锁了,本地复现率也很高,经过我多次尝试, 发现竟是我的结束判断有问题!我一开始是在结束时产生interrupt信号将wait中断,导致了我在第三次作业有一个循环并不会被interrupt断掉,而在出了while循环后错过了中断时机导致陷入无限的等待中,基于此问题,我只能新增了一个interrupt变量,通过进行变量的判断来选择什么时候出循环、结束线程等。

在hack别人的道路上, 我走得也很艰辛。首先,其它同学的代码逻辑有些与我完全不同,读懂别人代码后我就处于一种很懵的状态了,试了一些自己觉得模糊的点却也发现人家的代码都是对的,有些性能不是很好但我也hack不到,考虑到这次数据的复杂性, 我不认为评测机能起到很好的作用(第一次我也每靠评测机hack到别人,反而是自己构造的数据更容易hack到别人),所以我没有花时间去建评测机。

其次,我尝试了一些我认为可能会出现调度问题的测试点,但可能我的想法还是不够全面,我在这个方向上也没有hack到别人。关于死锁问题,直到最后,我才意识到是怎么一回事,更加无法通过这个去hack别人。

总之,这次的互测我是比较失败的,没有很好get到这次互测的精髓所在,这和我不扎实的多线程的知识有关,只想着通过设计策略去hack别人而没有利用多线程的程序特点,还没有从第一单元走出来。

五、心得体会

在本单元的设计中, 我没有特意去采取高超的算法去优化,也没有去对每种换乘策略进行设计,只是做了一些必要的优化,比如第二次作业我的所有电梯都会跑向有需要的楼层,但只有一台电梯会开门 ,这是考虑到需求所在楼层产生的随机性,所以并没有只让一台电梯去接人;第三次作业,我也仅是在电梯申请需求时,判断换乘需求是否可以通过此电梯进行换乘,并没有提前为每位乘客设计最优的换乘策略,这也是考虑到防止有些电梯过忙而延长乘客等待时间,得不偿失。总之,虽然我的优化工作很少,但也是在合理的考虑之下的,虽然肯定比不上优化到极致的大佬,但我也发现我的策略最后得到了较高的性能分,这也印证了随机是个好东西。

关于线程安全的问题,我也得到了很多自己的血泪经验。从一开始的乱加锁,无头无脑地找死锁bug,到最后的基本能经过简单的输入定位到死锁位置,我觉得我收获了很多,也更加体会到了多线程的玄妙,亲身体会了线程安全的重要性。

2020北航OO第二单元总结的更多相关文章

  1. 北航OO第二单元作业总结(2.1~2.3)

    在经过第一单元初步认识面向对象编程思想后,本蒟蒻开始了第二单元--多线程部分的学习.本单元的作业是构造符合条件的"目的选层电梯"模型,自行设计调度算法,进行合理调度,完成所有乘客的 ...

  2. 2019年北航OO第二单元(多线程电梯任务)总结

    一.三次作业总结 1. 说在前面 对于这次的这三次电梯作业,我采用了和几乎所有人都不同的架构:将每个人当作一个线程.这样做有一定的好处:它使得整个问题的建模更加自然,并且在后期人员调度变得复杂时,可以 ...

  3. 2020北航OO第一单元总结

    前言 学习面向对象这门课程的后的第一单元作业,主线是多项式求导,三次作业层层推进,由单一的幂函数求导,到幂函数和三角函数的复合求导,最后再到两种函数的嵌套求导,由两个类到重构后的十几个类,我逐渐对面向 ...

  4. 北航OO第二单元——电梯调度

    三次作业要求简介 特点:目的选层电梯 在电梯的每层入口,都有一个输入装置,让每个乘客输入自己的目的楼层.电梯基于这样的一个目的地选择系统进行调度,将乘客运送到指定的目标楼层. 第一次: 在任意时刻输入 ...

  5. 北航OO第二单元总结

    电梯调度的设计策略 第一次作业是单部多线程傻瓜电梯 这次作业的电梯名副其实是一部傻瓜电梯,每次只能运一个人.出于线程安全的考虑,选择了阻塞队列.然后按照先来先服务的原则服务下一个指令.没有什么复杂的设 ...

  6. 2020北航OO第四单元总结

    2020北航OO第四单元总结 一.本单元架构设计 本单元作业是实现一个UML图解析器,其中实现接口及主要框架课程组已经提供,只需要我们完成特定功能. 在第一次作业时,感到十分迷茫,不知道如何下手,最后 ...

  7. 2020北航OO第三单元总结

    2020北航OO第三单元总结 本单元要求是根据JML规格完善代码,初看是一个简单的代码照搬实现的东西,但最后才发现由于CPU时间的限制,还考察了大量优化策略及数据结构中关于图的知识,是一次非常注重细节 ...

  8. oo第二单元作业总结

    oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...

  9. OO第二单元优化博客

    OO第二单元优化博客 第五次作业没有性能分,但是,我在这一单元的宗旨就是写一个日常生活中 最常见的那种电梯,所以第五次我没有写傻瓜电梯,而是直接写了个\(look\),和第六次基本相同. 总计一下lo ...

随机推荐

  1. Docker 一次性进程与对话进程

    目录 一次性进程 对话进程 退出的方法 参考 Docker在运行程序的时候,需要区分运行的程序是一次性进程还是对话进程,不同的进程操作方式有差异. 一次性进程 一些简单进程是不需要交互的,比如hell ...

  2. 剑指 Offer 34. 二叉树中和为某一值的路径 + 记录所有路径

    剑指 Offer 34. 二叉树中和为某一值的路径 Offer_34 题目详情 题解分析 本题是二叉树相关的题目,但是又和路径记录相关. 在记录路径时,可以使用一个栈来存储一条符合的路径,在回溯时将进 ...

  3. SpringBoot启动流程分析原理(一)

    我们都知道SpringBoot自问世以来,一直有一个响亮的口号"约定优于配置",其实一种按约定编程的软件设计范式,目的在于减少软件开发人员在工作中的各种繁琐的配置,我们都知道传统的 ...

  4. Windows-Redis占用C盘系统空间

    发现redis在电脑死机蓝屏的情况下,就是非正常退出redis会导致redis的缓存文件不会回收,占用系统空间, 下次在启动的时候,会再次创建一个10G多的缓存文件,极度占用磁盘空间. 现说明解决办法 ...

  5. go中sync.Once源码解读

    sync.Once 前言 sync.Once的作用 实现原理 总结 sync.Once 前言 本次的代码是基于go version go1.13.15 darwin/amd64 sync.Once的作 ...

  6. 强制断开ssh连接出现ssh崩溃问题

    出现原因 finalshell意外终止,导致ssh连接意外终止 之后怎么都连不上虚拟机的ssh,一看是虚拟机的ssh已经被意外暂停,可能是跟finalshell的意外终止有关 解决 chmod 600 ...

  7. 从一部电影史上的趣事了解 Spring 中的循环依赖问题

    title: 从一部电影史上的趣事了解 Spring 中的循环依赖问题 date: 2021-03-10 updated: 2021-03-10 categories: Spring tags: Sp ...

  8. java基础:变量、常量与作用域

    变量就是可以变化的量,每个变量都必须声明其类型,Java 变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域.作用域 类变量 实例变量 局部变量常量初始化后不能在改变值,不会变动的值,它 ...

  9. 「NOIP模拟赛」Round 2

    Tag 递推,状压DP,最短路 A. 篮球比赛1 题面 \(Milky\ Way\)的代码 #include <cstdio> const int N = 2000, xzy = 1e9 ...

  10. SpringMVC源码分析和启动流程

    https://yq.aliyun.com/articles/707995 在Spring的web容器启动时会去读取web.xml文件,相关启动顺序为:<context-param> -- ...