2020北航OO第二单元总结
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第二单元总结的更多相关文章
- 北航OO第二单元作业总结(2.1~2.3)
在经过第一单元初步认识面向对象编程思想后,本蒟蒻开始了第二单元--多线程部分的学习.本单元的作业是构造符合条件的"目的选层电梯"模型,自行设计调度算法,进行合理调度,完成所有乘客的 ...
- 2019年北航OO第二单元(多线程电梯任务)总结
一.三次作业总结 1. 说在前面 对于这次的这三次电梯作业,我采用了和几乎所有人都不同的架构:将每个人当作一个线程.这样做有一定的好处:它使得整个问题的建模更加自然,并且在后期人员调度变得复杂时,可以 ...
- 2020北航OO第一单元总结
前言 学习面向对象这门课程的后的第一单元作业,主线是多项式求导,三次作业层层推进,由单一的幂函数求导,到幂函数和三角函数的复合求导,最后再到两种函数的嵌套求导,由两个类到重构后的十几个类,我逐渐对面向 ...
- 北航OO第二单元——电梯调度
三次作业要求简介 特点:目的选层电梯 在电梯的每层入口,都有一个输入装置,让每个乘客输入自己的目的楼层.电梯基于这样的一个目的地选择系统进行调度,将乘客运送到指定的目标楼层. 第一次: 在任意时刻输入 ...
- 北航OO第二单元总结
电梯调度的设计策略 第一次作业是单部多线程傻瓜电梯 这次作业的电梯名副其实是一部傻瓜电梯,每次只能运一个人.出于线程安全的考虑,选择了阻塞队列.然后按照先来先服务的原则服务下一个指令.没有什么复杂的设 ...
- 2020北航OO第四单元总结
2020北航OO第四单元总结 一.本单元架构设计 本单元作业是实现一个UML图解析器,其中实现接口及主要框架课程组已经提供,只需要我们完成特定功能. 在第一次作业时,感到十分迷茫,不知道如何下手,最后 ...
- 2020北航OO第三单元总结
2020北航OO第三单元总结 本单元要求是根据JML规格完善代码,初看是一个简单的代码照搬实现的东西,但最后才发现由于CPU时间的限制,还考察了大量优化策略及数据结构中关于图的知识,是一次非常注重细节 ...
- oo第二单元作业总结
oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...
- OO第二单元优化博客
OO第二单元优化博客 第五次作业没有性能分,但是,我在这一单元的宗旨就是写一个日常生活中 最常见的那种电梯,所以第五次我没有写傻瓜电梯,而是直接写了个\(look\),和第六次基本相同. 总计一下lo ...
随机推荐
- C#日志使用
本文参考链接 日志框架 框架选择:NLog 安装方法,Nuget命令行:Install-Package NLog 常用规则 尽量不要在循环中打印日志. 应输出错误的堆栈信息:e.Message仅为异常 ...
- 解决vue 绑定事件会覆盖默认参数的问题
解决vue 绑定事件会覆盖默认参数的问题 在使用一些ui框架的时候,某些组件的框架中的事件所规定的参数不能满足实际开发的需要,但是直接传入参数会把默认的参数覆盖掉 解决方法:将参数放入箭头函数中,传递 ...
- vue封装一个弹框组件
这是一个提示框和对话框,例: 这是一个组件 eject.vue <template> <div class='kz-cont' v-show='showstate'> &l ...
- Java 语言基础 01
语言基础·一级 什么是计算机? 计算机(Computer)全称:电子计算机,俗称电脑.是一种能够按照程序运行,自动.高速处理海量数据的现代化智能电子设备.由硬件和软件所组成,没有安装任何软件的计算机称 ...
- Python--入门接口测试(1)
1. 什么是接口测试?为什么要做接口测试? 接口测试是测试系统组件间接口的一种测试.接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点.测试的重点是要检查数据的交换.传递和控制管理过 ...
- MediaCodec编码结合FFmpeg封装流
在Android平台上合成视频一般使用MediaCodec进行硬编码,使用MediaMuxer进行封装,但是因为MediaMuxer在某些机型上合成的视频在其他手机上播放会出现问题,而且只支持一个音频 ...
- WinForm的Socket实现简单的聊天室 IM
1:什么是Socket 所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象. 一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制. 从 ...
- 【图像处理】OpenCV+Python图像处理入门教程(四)几何变换
这篇随笔介绍使用OpenCV进行图像处理的第四章 几何变换. 4 几何变换 图像的几何变换是指将一幅图像映射到另一幅图像内.有缩放.翻转.仿射变换.透视.重映射等操作. 4.1 缩放 使用cv2. ...
- 让你弄懂js中的闭包
目录 闭包 闭包如何产生 闭包是什么 常见的闭包 闭包的作用 闭包的生命周期 闭包的应用 闭包的缺点 内存泄露 内存溢出 闭包面试题 闭包 之前在我执行上下文执行上下文栈这篇文章中,出现了这样一个题目 ...
- Tex中的引号(JAVA语言)
package 第三章; import java.util.Scanner; public class Tex中的引号 { public static void main(String[] args) ...