北航OO第二单元——电梯调度
三次作业要求简介
特点:目的选层电梯
- 在电梯的每层入口,都有一个输入装置,让每个乘客输入自己的目的楼层。电梯基于这样的一个目的地选择系统进行调度,将乘客运送到指定的目标楼层。
第一次:
- 在任意时刻输入一个或多个请求,包括出发层、目的层、乘客的id(每一个乘客有独有的id,且我们设计的系统是直到这个id的)
- 电梯可以控制谁能出电梯、谁能进电梯
- 在到达楼层、开门关门、出人进人时输出
- 第一次作业是单电梯,电梯能够在1-20楼运行
第二次:
- 初始有3部电梯,可以通过指令增加1-2部电梯
第三次:
有三种电梯
型号 到达楼层 移动速度 限乘人数 A 1-20 0.6s/层 8 B 奇数层 0.4s/层 6 C 1-3,18-20 0.2s/层 4 初始状态有A、B、C三种型号各一部,可以通过指令增加任意类型的电梯
设计过程分析
我认为,这一单元的主要设计难点有二:
- 确定线程数量和各线程控制的类
- 确定使用何种形式对电梯进行控制
一旦确定了这两点,经过第一单元训练的你应该就能很轻松的做好类的规划,并写出整个作业了。我想你应该对我是如何得出最终设计没有兴趣,所以下面来直接说说我的设计
作业设计
类图
类的关系概要
MInputThread
- 正如其名,这个类主要用来不断地从标准输入读取指令,并将其加入指令队列
MRequestQueue
中 - 这是一个线程
- 正如其名,这个类主要用来不断地从标准输入读取指令,并将其加入指令队列
MDispatcher
- 指令分配器。使用单例模式,整个系统只有一个分配器
MDispatcher
持续尝试从MRequestQueue
中获取新的请求,并决定将这个请求分配给哪一个电梯的MController
- 注意:我的算法是直接将请求分配过去,而不是将一条指令分配过去。电梯控制器接收到该请求如何执行是
MController
的事情 - 在第三次作业中,这个类还先尝试将指令拆分,实现乘客的换乘
- 这是一个线程。当
MInputThread
在等待下一条输入时,可以继续利用CPU,实现指令的分配
MController
- 电梯控制器,从维护一个
MRequestList
,从MDispatcher
接受请求,控制电梯的移动 - 一个电梯有一个电梯控制器
- 电梯控制器,从维护一个
MElevator
- 电梯类,在本单元作业中仅仅存储电梯的最大人数、运行一层的时间、开门关门的时间等。
- 我们并不控制实际存在的电梯,为了充分的可拓展性,设立
MElevator
- 在第三次作业中,此类被设置为抽象类,并编写了三个不同类型电梯的子类
MOutput
- 类如其名,输出类,简化算法中输出语句的复杂度
MSeparator
- 拆分指令的类,其中只包含静态方法,因为没有自己的成员变量。
MRequestQueue
- java的
Queue
的包装类,并增加了isClose()
方法,标记输入是否已经结束,从而结束各个线程
- java的
MRequestList
- 每一个电梯控制器拥有一个
MRequestList
,存放着要处理的请求 MRequestList
中存放了两个链表upList
和downList
,分别存放上楼的请求和下楼的请求,并按照起始楼号排序- 有一个
head
,指向下一条要处理的指令 - 注意:以上三条仅适用于单一的运行算法,若有其他算法可以在此类中调整
- 每一个电梯控制器拥有一个
算法设计
以下算法之间是相互独立的,可以更改任意一个不影响其他算法的运行。这也是符合面向对象程序设计思想的
请求分配算法
MDispatcher
负责该算法,采用贪心算法进行请求分配- 每当得到一条请求,遍历所有电梯(第三次作业中,变为能够运行该请求的电梯),将其
clone
两份,一份保持原状运行到电梯内的MRequestList
为空且电梯内没有人的总时间,一份添加该请求后再计算总时间。两个时间的差值为运行该指令需要增加的时间。找到所有电梯中增加时间最小的,将该条请求分配给他 - 注意:
- 在
clone
之后,电梯的状态可能已经发生了变化,但是可能性相对较小,因此及时产生误差也不要紧,可以将指令继续分配给相应的电梯 - 此处贪心贪的是增加的时间,也可以贪增加该请求后的运行时间,从理论无法证明两种贪心哪一种好,但是实践证明前者好一些
- 在
电梯运行算法
采用基础的捎带原则,和常见的电梯一致
每次电梯保持一个方向运行,沿路捎带同方向的请求,直到该方向上没有请求且电梯内的人已经全部离开,再调转方向
该算法是通过由
MRequestList
中维护的特殊循环链表实现的,将所有请求适当排序,每一次的head
指向的位置就是该算法下电梯下一个要处理的请求注意:
要时刻注意可拓展性,减少单个方法的复杂度。比如
MController
(电梯控制器)的run()
方法大概可以写成public void run () {
if (hasPersonIn || hasPersonOut) {
openDoor();
outPerson();
inPerson();
closeDoor();
}
if (hasNextMission()) {
goNextFloor();
}
}
这样可以最大程度保留可读性和可扩展性,不需要注释就能看懂整个算法的框架
请求拆分算法
- 我没有采用一些能在理论上达到最短路的算法,而是手动计算各种常见情况,判断是否需要拆分指令换乘
- 具体拆分算法如下:
- 条件:能用B、C电梯承载的就不换乘;换乘至多一次
- 因此将请求分为不换乘、通过AB换乘、通过BC换乘、通过AC换乘四种情况
- 对于需要换乘的请求,计算通过AB、BC、AC换乘和不换乘(也就是只通过A)所需的时间(如果不能换乘则返回
Long.MAX_VALUE
),选择时间最短
- 换乘方法如下:
MSeparator
将指令拆分为一个ArrayList<PersonRequest>
数组返回给MDispatcher
,如果数组长度大于1,则将第一项作为key,第二项作为value加入一个HashMap
中,第二项和第三项等等以此类推- 每当电梯让一个请求出电梯时,使用观察者模式通知并将该请求传递给
MDispatcher
,MDispatcher
拿到后去HashMap
中寻找,如果找到对应的value
,则将新的value
分派给电梯,并将此键-值对删除
多线程的经验
在看以下的内容之前,建议你最好已经自己动手写完了电梯,进行了一定的试错和碰壁后,再阅读或许能够有更大的收获
摘要
synchronized
块能多小就多小!
一定要保证在不得不用的那一行再使用
synchronized
,而且一旦在逻辑上不再需要该对象,或者是该对象改变了也不影响的时候,就赶紧退出synchronized
,把锁释放掉不要一个
synchronized
把整个方法都框起来,这样极大地降低了运行效率;另外,不要随便
wait()
!wait()
极其容易产生死锁,一定要自己充分考虑后再使用wait()
bug分析
- 第一次作业卡点交上去结果全部TLE,原因是产生了死锁
- 后期分析原因,是因为
wait() notifyAll()
的组织方式出现了问题,很可能在没有wait的时候notify了,从而产生notify信号丢失。 - 这是由于算法中对
synchronized
块的使用过于简单粗暴,该释放的时候没有释放掉,一直拿着锁,从而使运行逻辑产生奇妙的变化 - 互测阶段并没有看或者测别人的代码,因此没有相关经验
心得体会
线程安全:
- 在上面的 多线程经验 已经说过
层次化设计
- 多线程的层次设计要比多项式困难的多,因为是三个线程同步运行,大大增加了设计难度。
- 我自认为设计的比较有层次性,能保留扩展性的地方都保留了,三次作业并没有大的重构过
- 然而,模块之间的关系还是有一些丑陋,不是完全的面型对象。比如在贪心算法中,
MDispatcher
调用MController
的clone()
,将其拷贝一份再调用拷贝对象的getRunTimeWithRequest(PersonRequest)
方法(获得加上一个请求后的运行时间)获得运行时间 - 这一段的代码逻辑总让我决定不美,但我无法说出哪里不美,也不知道该如何修改。如果你能看出来,可以告诉我
- 邮箱:13061262721@163.com
三个周的时间,让所有人的多线程设计能力、面向对象设计能力同时得到极大提高,不得不说课程组真是高啊
北航OO第二单元——电梯调度的更多相关文章
- 2020北航OO第二单元总结
2020北航OO第二单元总结 前言 本单元考察基于多线程的电梯调度问题,成功让我从一个多线程小白到了基本掌握了使用锁来控制线程安全的能力,收获颇多(充分体验了迷茫地de一个又一个死锁bug的痛苦). ...
- OO第二单元(电梯)单元总结
OO第一单元(求导)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉理解和掌握多线程的思想和方法.这个单元以电梯为主题,从一开始的最简单的单部傻瓜调度(FAFS)电梯到最后的多部 ...
- OO第二单元电梯作业总结
目录 目录一.第一次作业分析设计策略基于度量分析程序结构二.第二次作业分析设计策略基于度量分析程序结构三.第三次作业分析设计策略基于度量分析程序结构四.分析自己程序的bug五.发现别人程序bug所采用 ...
- OO第二单元电梯线程系列总结作业
电梯系列第一次作业 功能描述: 傻瓜电梯无需考虑超载捎带 线程模式: Producer-Consumer Pattern 思路: 第一次作业是一个傻瓜电梯,分别有一个生产者生成电梯指令(也就是Inpu ...
- 北航OO第二单元作业总结(2.1~2.3)
在经过第一单元初步认识面向对象编程思想后,本蒟蒻开始了第二单元--多线程部分的学习.本单元的作业是构造符合条件的"目的选层电梯"模型,自行设计调度算法,进行合理调度,完成所有乘客的 ...
- 北航OO第二单元总结
电梯调度的设计策略 第一次作业是单部多线程傻瓜电梯 这次作业的电梯名副其实是一部傻瓜电梯,每次只能运一个人.出于线程安全的考虑,选择了阻塞队列.然后按照先来先服务的原则服务下一个指令.没有什么复杂的设 ...
- 2019年北航OO第二单元(多线程电梯任务)总结
一.三次作业总结 1. 说在前面 对于这次的这三次电梯作业,我采用了和几乎所有人都不同的架构:将每个人当作一个线程.这样做有一定的好处:它使得整个问题的建模更加自然,并且在后期人员调度变得复杂时,可以 ...
- OO第二单元——电梯作业总结
前言 本单元作业主要以设计电梯来实现多线程编程.本章主要学习了如何使用多线程以及如何确保多线程安全,从电梯的调度策略中学会了如何简单地使用synchronized锁来控制线程安全. 首先,明确锁的两个 ...
- OO第二单元多线程电梯总结
OO第二单元多线程电梯总结 第一次作业 设计思路 Input为输入线程,负责不断读取请求并将读到的请求放入调度器中. Dispatcher为调度器,是Input线程和Elevator线程的共享对象,采 ...
随机推荐
- php自动识别背景并且把它改为白色
此源码有个阈值可以自己调节,精确度等自测 <?php /*$Colorimg = new Colorimg(); $image=$Colorimg->IMGaction("G:/ ...
- elf文件结构解读以及plt节got节的理解
前言: 熟悉elf文件结构是一件很不错的事,因为安卓中的so加固以及修复都是需要这些知识的,包括pwn里面的rop之类的,也都是 和got节,plt节息息相关的,个人建议是在搞懂elf文件结构后,自己 ...
- hugo + nginx 搭建博客记录
作为一个萌新Gopher,经常逛网站能看到那种极简的博客,引入眼帘的不是花里胡哨的图片和样式,而是黑白搭配,简简单单的文章标题,这种风格很吸引我.正好看到煎鱼佬也在用这种风格的博客,于是卸载了我的wo ...
- ES6 箭头函数你正确使用了吗
ES6 箭头函数你正确使用了吗 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 说明 在ES6中允许使用"箭头&quo ...
- Spring cloud中相关的工具和库
spring: 是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架. spring mvc: spring集成的mvc开发框架. spring ...
- java基础---数组的查找算法(2)
一.查找的基本概念 查找分为有序查找和无序查找,这里均以数组为对象,有序查找指的是数组元素有序排列,无序查找指的是数组元素有序或无序排列 平均查找长度(Average Search Length,AS ...
- Jmeter常见报错信息: ERROR - jmeter.protocol.http.proxy.ProxyControl: Could not initialise key store java.io.IOException: Cannot run program "keytool"
JMeter 2.10 用的新方法来录制HTTPS请求Java 7. 录制的过程中会碰到一些问题或者报错,就目前碰到的,做出一些总结. ERROR - jmeter.protocol.http.pro ...
- ffmpeg入门篇-滤镜的基本使用
转发自白狼栈:查看原文 滤镜 什么是滤镜?百度百科介绍说"滤镜主要是用来实现图像的各种特殊效果......". 我们最早在ffmpeg是如何转码的一文中了解过滤镜,来回顾下当时的转 ...
- [刘阳Java]_步步窥探JS变量作用域
今天的这个文章题目名称甚是让人会突发异想.JS变量作用域是务必需要搞懂的,单从面试过程就会让面试者烧脑壳.所以,我们还是写一篇关于JS变量作用域的技术专题,让所有小伙伴能够借此文章去整理JS的基础学习 ...
- 简单设计一个onedata指标管理体系
以阿里云的maxcompute的数据仓库架构为例, 从上往下定义, dwp的数据,来源是dws+dim,最主要是dws.这里不讨论dim的作用. dws的数据来源于dwd. dwd的数据来源于ods. ...