OO_Lab1

问题描述

模拟多线程实时电梯系统,新主楼ABCDE五个楼座各楼层均有电梯,乘客发起形如“从X座x层到Y座y层”的请求,电梯模拟上下行、开关门、乘客进出等行为,以满足所有乘客的要求。

解决思路

各个电梯无论是具体行为还是调度请求都相互独立,因此可以采用多线程的设计思想,每个电梯建立一个线程,这样电梯的行为都可以在线程内完成,可以非常方便的使用 sleep, wait, notifyAll 等函数来模拟消耗时间的操作和等待请求,同时输入也可以采用一个线程来处理。

整体的架构方面,三次作业我都采用了生产者-消费者模型,也就是刚才所提,输入作为生产者写为线程,该线程不断读取输入并提供给托盘;电梯作为消费者处理托盘的输入,每个电梯也写为一个线程;托盘模拟了容纳未处理的请求的容器,供输入线程与电梯线程交互使用。托盘的所有方法全部加锁以防止出现线程安全错误。

由于我在大部分情况下都采用了自由竞争策略,因此不需要显示的设置调度器,而是将请求的分解和分配分别放在输入和电梯线程中,而托盘只设置 addRequest, query, get 等操作。

第五次作业

这次作业较为简单,输入只涉及提供请求,电梯从楼座中选择请求接受,楼座(即托盘)将请求分类存储。

UML类图:

时序图:

后两次作业UML时序图与之相似,不再额外画出。

第六次作业

这次作业新增了横向电梯,但是其实没有什么本质改变,我采用了建立电梯抽象类的方法,仅将少部分横向、纵向电梯不同的方法设置为抽象方法,大部分电梯开关门、上下行、乘客进出对于两种电梯没有区别,可以在抽象电梯类中实现。同时将楼座和楼层抽象为容器(Container)和站点(Station)。对于加入新电梯的请求,只要新开一个对应线程即可。

UML类图:

第七次作业

首先自定义的要求非常容易处理,只要将那些部分从常量变成变量或方法即可。

难点在于请求的分解,我采用了静态分解的方式,也就是在请求被发起时就将其分解为若干横向和纵向请求,因为具体的数据分布无法得知,因此采用静态分配能较大幅度减少编码复杂度。

UML类图:

架构分析

基于度量的分析

对第七次作业进行基于度量的分析(只保留部分):

method CogC ev(G) iv(G) v(G)
code.Elevator.run() 25.0 7.0 10.0 16.0
code.CompoundRequest.solve() 19.0 1.0 12.0 12.0
code.FloorElevator.getNextState(int) 16.0 7.0 7.0 12.0
code.Elevator.checkEnter() 13.0 4.0 6.0 8.0
code.BuildingElevator.getNextState(int) 12.0 9.0 5.0 9.0
code.Elevator.checkExit() 7.0 4.0 4.0 6.0
code.Container.connect(char, char) 6.0 4.0 4.0 6.0
code.PassengerRequester.run() 6.0 3.0 5.0 5.0
code.DirectRequest.DirectRequest(int, int, char, char, Container) 5.0 2.0 2.0 5.0
code.PassengerRequester.addRequest(Request) 5.0 1.0 3.0 3.0
code.Elevator.doorOpen() 4.0 3.0 3.0 5.0
code.Elevator.moveDown() 4.0 2.0 4.0 5.0
code.Elevator.moveUp() 4.0 2.0 4.0 5.0
code.Elevator.passengerExit(CompoundRequest) 4.0 2.0 4.0 5.0
code.Station.getDown(Elevator) 4.0 3.0 4.0 4.0
code.Station.getUp(Elevator) 4.0 3.0 4.0 4.0
code.Station.queryDown(Elevator) 4.0 3.0 3.0 4.0
code.Station.queryUp(Elevator) 4.0 3.0 3.0 4.0
code.Elevator.elevatorEmpty() 3.0 3.0 2.0 3.0
code.Elevator.passengerEnter(CompoundRequest) 3.0 2.0 3.0 4.0
code.Main.main(String[]) 3.0 1.0 4.0 4.0
code.PassengerRequester.addCompoundRequest(CompoundRequest) 3.0 2.0 2.0 3.0
code.CompoundRequest.getFirstRequest() 2.0 2.0 2.0 2.0
code.CompoundRequest.tim(int) 2.0 1.0 1.0 3.0
code.Container.waitForNext() 2.0 1.0 3.0 3.0
code.DirectRequest.buildingName() 2.0 2.0 2.0 3.0
code.DirectRequest.floorName() 2.0 2.0 2.0 3.0
code.DirectRequest.getFromStation() 2.0 2.0 2.0 2.0
code.DirectRequest.getToStation() 2.0 2.0 2.0 2.0
code.DirectRequest.up() 2.0 2.0 1.0 2.0
code.Elevator.doorClose() 2.0 2.0 2.0 3.0
code.Station.addRequest(CompoundRequest, boolean) 2.0 1.0 2.0 2.0
Total 187.0 142.0 176.0 220.0
Average 2.174418 1.651162 2.046511 2.558139

其中复杂度最高的几个方法 Elevator.runElevator.checkEnter()Elevator.checkExit()是电梯运行的核心部分, CompoundRequest.solve(), FloorElevator.getNextState(int), BuildingElevator.getNextState(int) 是电梯调度的核心部分,这两部分决定了大部分电梯运行的主要逻辑,也是我编写代码主要思考的部分。

总的来讲,这些复杂度数值还在可以接受的范围内。

优点

  1. 思路清晰:各个电梯分为各个线程,各个电梯只需处理自己的行为,极大简化了代码编写。电梯、输入、托盘各司其职职责明确,分别提供有限的接口用于线程交互,在编码时不容易出错。各类耦合程度低而内聚程度高。
  2. 抽象程度高:将两类电梯都抽象为电梯实现上下行、开关门、乘客进出;将楼层楼座都抽象为容器和站点的组合,只需使用同一套代码即可解决这两类问题,抽象程度到减少了代码量,也降低了编码和调试难度。
  3. 线程更安全:输入线程与电梯线程、电梯线程之间不直接交互,而是全部通过容器来进行交互,因此只要两类线程都只调用容器的方法,同时对容器加锁,那么就理论上不存在线程安全问题。这可以简化线程的交互,也不用考虑特殊情况是否会出问题。电梯的停止也是由容器来判断。

细节实现

1、复杂请求的分解

如果是纵向请求,则直接加入对应容器,否则选择总距离最短、换乘次数最少、接受任务最少的横向电梯作为中转,代码如下:

public void solve() {
steps.clear();
if (this.fromBuilding == this.toBuilding) {
Container container = containers.get(fromBuilding);
steps.add(new DirectRequest(fromFloor, toFloor, fromBuilding, toBuilding, container));
container.addUnfinishedRequest();
} else {
int dis = Integer.MAX_VALUE;
int tim = Integer.MAX_VALUE;
int unf = Integer.MAX_VALUE;
int floor = 1;
for (int i = 1; i <= Main.getFloorCount(); ++i) {
Container container = containers.get(Main.floorIdToName(i));
if (container.connect(fromBuilding, toBuilding)) {
if (dis(i) < dis) {
floor = i;
dis = dis(i);
tim = tim(i);
unf = container.getUnfinishedRequest();
} else if (dis(i) == dis && tim(i) < tim) {
floor = i;
tim = tim(i);
unf = container.getUnfinishedRequest();
} else if (dis(i) == dis && tim(i) == tim && container.getUnfinishedRequest() < unf) {
floor = i;
unf = container.getUnfinishedRequest();
}
}
}
if (this.fromFloor != floor) {
Container container1 = containers.get(fromBuilding);
steps.add(new DirectRequest(fromFloor, floor, fromBuilding, fromBuilding, container1));
container1.addUnfinishedRequest();
}
Container container2 = containers.get(Main.floorIdToName(floor));
steps.add(new DirectRequest(floor, floor, fromBuilding, toBuilding, container2));
container2.addUnfinishedRequest();
if (floor != this.toFloor) {
Container container3 = containers.get(toBuilding);
steps.add(new DirectRequest(floor, toFloor, toBuilding, toBuilding, container3));
container3.addUnfinishedRequest();
}
}
}

2、线程的结束

线程的结束可以理解为特殊的请求,即输入线程请求结束整个程序,然后由容器(托盘)向电梯发出停止的请求,电梯在执行完所有请求后结束自身。

PassengerRequester:

for (Character key : containers.keySet()) {
containers.get(key).requestTerminate();
}

Container:

private void checkStop() {
if (stop()) {
notifyAll();
}
} public synchronized boolean stop() {
return requestTerminate && unfinishedRequest == 0;
} public synchronized void requestTerminate() {
requestTerminate = true;
checkStop();
}

Elevator:

private boolean elevatorEmpty() {
for (Queue<CompoundRequest> q : this.acceptedRequests) {
if (!q.isEmpty()) {
return false;
}
}
return true;
} private boolean checkStop() {
return container.stop() && elevatorEmpty();
}

3、线程通信

采用 wait-notify 机制,在 Container 里进行通信:

public synchronized void addRequest(CompoundRequest compoundRequest) {
stations.get(compoundRequest.firstRequestFromStation())
.addRequest(compoundRequest,compoundRequest.firstRequestDirection());
notifyAll();
} public synchronized void waitForNext() {
try {
if (!stop()) {
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

性能策略

1、电梯的请求查询

对于每个楼层、楼座,将所有请求按每层上下分到各个队列里。电梯使用 getNextState 来接收新请求。对于纵向电梯,优先选取在其上方向上的请求,对于横向电梯,优先选取离其最近的请求。

2、横向请求方向

我采用了将所有请求视为同一方向的策略。

3、多个电梯任务分配

我采用了自由竞争的方式,即各个电梯同时去找各个请求,先到先得,这样在简化代码难度的情况下也能保证不错的性能。

\[bug惩罚分=未被修复的bug数\times\alpha+非合并修复对应的bug数+合并修复次数,\quad其中\alpha=2
\]

代码测试

我采用了随机生成数据与手动测试相结合的方式,通过随机大量数据来覆盖大部分错误,同时手动针对电梯超载、在错误楼层停下等bug进行测试。

发现的bug

1、多线程下,不能错误的认为已经判断过的情况接下来不会再发生。

我使用了 checkEnter 函数判断了是否有乘客进入,接下来错误的认为只有电梯移动和空等的可能,但是实际上可能在 checkEnter 后出现请求,使得我的电梯错误运行。

OO_Lab1总结博客的更多相关文章

  1. Android请求网络共通类——Hi_博客 Android App 开发笔记

    今天 ,来分享一下 ,一个博客App的开发过程,以前也没开发过这种类型App 的经验,求大神们轻点喷. 首先我们要创建一个Andriod 项目 因为要从网络请求数据所以我们先来一个请求网络的共通类. ...

  2. 一步步开发自己的博客 .NET版(11、Web.config文件的读取和修改)

    Web.config的读取 对于Web.config的读取大家都很属性了.平时我们用得比较多的就是appSettings节点下配置.如: 我们对应的代码是: = ConfigurationManage ...

  3. 一步步开发自己的博客 .NET版(10、前端对话框和消息框的实现)

    关于前端对话框.消息框的优秀插件多不胜数.造轮子是为了更好的使用轮子,并不是说自己造的轮子肯定好.所以,这个博客系统基本上都是自己实现的,包括日志记录.响应式布局.评论功能等等一些本可以使用插件的.好 ...

  4. 【原】Github+Hexo+NextT搭建个人博客

    摘要 GitHub 是一个开源项目的托管网站,相信很多人都听过.在上面有很多高质量的项目代码,我们也可以把自己的项目代码托管到GitHub,与朋友们共享交流.GitHub Pages 是Github为 ...

  5. 我为什么要写LeetCode的博客?

    # 增强学习成果 有一个研究成果,在学习中传授他人知识和讨论是最高效的做法,而看书则是最低效的做法(具体研究成果没找到地址).我写LeetCode博客主要目的是增强学习成果.当然,我也想出名,然而不知 ...

  6. 博客使用BOS上传图片

    1.博客平台的选定 从大学开始做个人主页算起,最开始是使用html,CSSS写简单的页面,后面大学毕业之后接触到了WordPress,就开始用WordPress搭建网站.现在还维护着一个农村网站.ht ...

  7. 在jekyll模板博客中添加网易云模块

    最近使用GitHub Pages + Jekyll 搭建了个人博客,作为一名重度音乐患者,博客里面可以不配图,但是不能不配音乐啊. 遂在博客里面引入了网易云模块,这里要感谢网易云的分享机制,对开发者非 ...

  8. iOS controller解耦探究实现——第一次写博客

    大学时曾经做过android的开发,目前的工作是iOS的开发.之前自己记录东西都是通过自己比较喜欢的笔记类的应用记录下了.直到前段时一个哥们拉着我注册了一个博客.现在终于想明白了,博客这个东西受众会稍 ...

  9. 中文 iOS/Mac 开发博客列表

    中文 iOS/Mac 开发博客列表 博客地址 RSS地址 OneV's Den http://onevcat.com/atom.xml 一只魔法师的工坊 http://blog.ibireme.com ...

  10. 企业shell面试题:获取51CTO博客列表倒序排序考试题

    #!/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin HTMLFILE=/home/oldboy/ht ...

随机推荐

  1. Linux系统修改静态ip

    查看所有网卡 ip信息 ipconfig 修改网卡文件 vim /etc/sysconfig/network-scripts/ifcfg-eno1(网卡名) 新增语句 IPADDR=192.168.1 ...

  2. Cplex-opl解决网络路由选择和资源分配问题

    安装Cplex 注意事项:全英文系统.安装路径.代码,会减少软件运行设置错误.(该软件对中文支持性不好) opl语言基础 [转载]CPLEX学习笔记二 – OPL的数据类型 - 知乎 (zhihu.c ...

  3. Web前端单词大全

    style 修饰width 宽度height 高度title 想说明的text-align 水平对齐方式center 居中 left 居左 right 居右line-height 垂直对齐方式/行高 ...

  4. 自定义函数式@FunctionalInterface异常接口

    1.添加注解 /** * 抛出异常函数接口 * * @author liunancun * @date 2021/2/5 */ @FunctionalInterface public interfac ...

  5. 理解函数调用_使用arguments参数对所有函数参数执行操作

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. linux 部署python 系统服务管理命令 yum源设置 linux定时任务 python在linux的虚拟环境安装以及使用

    安装python3 三种方式 ==linux下很多脚本默认都用python2, 所以不要把python3的执行文件改为python,因为linux里默认python就是运行python2版本 == y ...

  7. GOF23种设计模式是哪些

    设计模式实践里面提供了许多经久不衰的解决方案和最佳方案.这里,GOF 设计模式主要分为三大类:创建模式.结构模式和行为模式.创建模式对于创建对象实例非常有用.结构模式通过处理类或对象的组合来作用于企业 ...

  8. Jmeter进行服务器性能压力测试遇问题及解决方案

    最近再给公司的一个项目进行服务器性能进行压测,要出一些报告图形展示,放弃了用boom工具我选择了用jmeter工具进行压测过程中遇到了一些问题下面将一一列出及解决方案希望帮助到你们!!! 1.装第三方 ...

  9. JS中split、slice、splice区别

    splite 定义:该方法是切割字符串的一种方法,该方法主要用于把一个字符串分割成字符串数组并且返回新生成的数组.用于字符串对象   语法:str.split(separator,howmany) 返 ...

  10. Java 一次操作多条数据

    //新增 <insert id="insertSelectiveList" useGeneratedKeys="true" parameterType=& ...