OO第二单元作业总结——多线程

单元任务

本单元主要的内容是通过模拟电梯的运行来熟悉多线程的实现,从简单的单部FAFS电梯开始,ALS电梯,到最后的多部ALS电梯。

一、设计策略分析总结

1.1 多线程协同

这三次的作业都需要通过多线程来实现输入和电梯执行的并发。因为吸取了第一单元的教训,在最开始设计的时候,就尽量考虑到之后的可扩展性,尽量使每个部分各司其职,相互之间减少关联性。

  • 第五次作业

第五次作业主要分为四个模块:主线程、输入线程、调度器即请求队列、电梯线程。

主线程主要是控制整体程序执行的。

输入线程的编写参照了课程组给出的demo,主要的功能是从标准输入中读取请求,并将请求添加到请求队列中。

因为是FAFS电梯,所以这次作业的调度器没有什么算法设计,电梯线程每次取请求的时候,将队列中的第一个请求返回即可,因此调度器只需要维护一个请求的动态数组,方法包括添加请求和取出请求。

电梯线程每次只取一个请求,在当前请求执行完毕之后,才会向调度器取下一个请求。

  • 第六次作业

本次作业的电梯为ALS电梯,即可捎带电梯,依旧是分为四个模块:主线程、输入线程、调度器、电梯线程。

与第五次作业的主要差别是调度器需要提供查询可捎带请求的功能,根据电梯当前的楼层数以及运行状态,来搜索请求队列中所有可能被捎带的请求,并将这些请求返回给电梯线程。具体的算法是:如果电梯处于上行状态,将请求队列中所有起始楼层与电梯当前楼层相等并且上行的请求添加到一个新的队列中,将这个队列返回,同时移除返回的请求;如果电梯处于下行状态,将请求队列中所有起始楼层与电梯当前楼层相等并且下行的请求添加到一个新的队列中,将这个队列返回,同时移除返回的请求。

因为考虑到性能优化,当电梯需要改变运行方向的时候,调度器为电梯线程提供了查找当前楼层之上的请求和当前楼层之下的请求的方法。这两个方法不会考虑请求的方向与电梯当前运动方向是否相符,而是根据起始楼层与当前楼层的关系,返回第一个满足条件的请求同时将其从队列中移除。

电梯线程需要维护当前楼层、运行状态、请求队列。当电梯中的队列为空时,向调度器取一个主请求,这个请求是还未执行的请求中到达时间最早的请求。电梯以主请求为主开始运行,因为此时主请求进入了队列但乘客还没有进入电梯,所以我还给电梯设置了一个Boolean型变量firstIn用于标记主请求是否已经进入电梯。当电梯中的队列不为空时,电梯开始运行直至队列再次为空。

每次运行时,首先根据firstIn判断主请求是否已经进入,如果没有,则从当前层数运行到主请求的起始层数,如果进入了,则从当前层数运行到主请求的目的层数。运行过程中,每到达一层,检测队列中有没有可以出去的乘客,再向调度器获取可以捎带的请求,加入队列。

每当电梯可能出现改变运行方向的时候,现在自己的队列中查找是否还有同方向的请求还未执行,如果有,将其设为主请求,即将该请求移至队列首部,继续执行;如果没有,向调度器查看运行方向上是否还有请求,如果有,以该请求为主请求继续执行,同时firstIn设为未进入,如果没有则改变方向继续执行队列中的请求。

  • 第七次作业

本次作业是同时调度三部电梯,同时每部电梯的停靠层数、限乘人数、运行速度均不相同。有一些请求是不能直接送达的,需要进行拆分,而拆分之后的请求是存在时间上的依赖关系的,例如,第二个请求必须在第一个请求执行完毕之后才可以写入请求队列中,不然可能会出现一个乘客同时出现在两部电梯中。为了处理拆分的请求,我创建了一个类TransRe对所有的请求进行包装。TransRe中只包含一个队列,用于存放拆分之后的多个请求。请求如果没有拆分,则对应的TransRe的队列中只有这一个请求,即长度为1;请求如果拆分成多个,则对应TransRe队列中会有多个请求,请求之间存在严格的先后关系。由此来判断出一个请求是否执行完毕。

主线程和输入线程的功能和前两次作业相同。

因为此次的调度器功能十分繁复,我将其分为了两层:上层调度器和下层调度器。

上层调度器的任务是将输入线程读到的请求根据不同电梯的停靠层数进行拆分,分配给各个电梯的下层调度器,同时通过集合维护还没执行完的请求的用户id。上层调度器的属性中包括所有与其对应的下层调度器,可以获得每部电梯的可停靠楼层,如果一个请求可以由一部电梯独立完成,则将该请求分配给该电梯调度器;如果不可以,则分别选取起始楼层可停靠电梯和目的楼层可停靠电梯,求两部电梯可停靠楼层的交集,由此划分请求,分配给起始楼层的电梯,将用户id加入集合中。如果新来的请求用户id在集合中,就将其从集合中移除,说明不再是划分请求。每个请求都会经过TransRe的包装。

下层调度器与第六次作业中的调度器功能相似,只不过变量类型由PersonRequest变为了TransRe,多维护了一个电梯可停靠楼层的集合,在查询可捎带请求时多出了数量要求。每个下层调度器都只为一部电梯服务。

电梯线程与第六次作业相似,多出了电梯编号、限乘人数、每层运行时间的属性,每次查询可捎带请求、向上或向下查询请求时,都需要判断电梯是否已满。每执行完一个请求,还要判断是否是划分请求,如果是,移除TransRe中的第一个请求,再写回上层调度器。

这三次作业中,我认为电梯线程的结束条件是一个十分重要的逻辑判断点。输入线程的判断条件课程组已经通过demo告诉我们了,而电梯线程何时结束关系到程序是否能正确运行。第五次和第六次的作业中,我的判断条件都是in.isAlive() || list.getLength() != 0,即输入线程没有结束或者请求队列不为空,意味着还有请求没有执行。但到第七次作业中,多部电梯时如果还使用这个判断条件会使程序进入忙等状态,当队列中剩余一个请求时,一部电梯取走了这个请求,其余两部电梯进入等待状态,可这时没有其他线程可以唤醒这两个线程,因此程序进入忙等状态,不会停止。

因为这个原因,我改变了判断条件,如果输入线程结束,并且上层调度器的未执行完请求集合为空,则向每个下层调度器中写入new PersonRequest(0, 0, 0)请求,这个请求不会被任何捎带查询返回,只会被获取主请求方法返回,因此电梯只要执行到该请求,说明请求已经全部执行完毕,可以结束线程了。

1.2 同步控制

对于多线程程序而言,最重要的是共享数据的同步控制。在这三次作业中,共享数据都是请求队列,第五次和第六次的作业都是一个总的请求队列,第七次作业的共享数据其实是三个下层调度器的请求队列。

因为输入线程的写操作和电梯线程的取操作都会对共享数据造成修改,所以这两个方法都需要加上synchronized进行修饰,即将共享数据锁住,使取和写不会同时进行,以此来保证共享数据是线程安全的。

二、基于度量分析程序结构

  • 第五次作业

2.1.1 代码规模

2.1.2 类图

2.1.3 类和方法复杂度

2.1.4 UML时序图

2.1.5 分析

可以看出,第五次作业还比较简单的,代码行数不是很多,每个类,每个方法的复杂度也不是很高,每个类相对独立,类中的属性也不是很多。

2.1.6 SOLID原则

[S] Single Responsibility Principle (单一功能原则):有较好实现,每个类和方法都有单一的功能。

[O] Open Close Principle(开闭原则):有较好实现,因为考虑到之后的可捎带和多电梯,模块划分的比较合理,扩展功能只需要添加方法即可。

[L] Liskov Substitution Principle(里氏替换原则):因为没有使用继承,所以不予讨论。

[I] Interface Segregation Principle(接口隔离原则):因为没有使用接口,所以不予讨论。

[D] Dependency Inversion Principle(依赖反转原则):有较好实现,三个主要的类内部的执行与另外两个类的内部执行没有依赖关系,依赖的是抽象的方法。

  • 第六次作业

2.2.1 代码规模

2.2.2 类图

2.2.3 类和方法复杂度

2.2.4 UML时序图

2.2.5 分析

可以看出,第六次作业与第五次作业在类的数量和类之间的关系上没有发生变化,但因为要实现可捎带功能,电梯线程和调度器的代码量有了明显的增长,电梯类过于复杂,导致OCavg和WMC过高,其中的很多方法也出现了复杂度过高的情况,这是因为我的调度基本上都是电梯来完成的,请求队列的调度只是提供了一些获取相关请求的方法,内部的执行顺序还是靠电梯自己来调整。其次还是因为我的功能划分不够细致,如果将每个功能都写一个方法,方法的复杂度应该会有明显的减少。

2.2.6 SOLID原则

[S] Single Responsibility Principle (单一功能原则):没有实现,电梯类过于复杂,其中的许多方法的都具有多个功能。

[O] Open Close Principle(开闭原则):有较好实现,因为考虑到之后多电梯,模块划分的比较合理,扩展为多电梯只需要创建出新的电梯线程对象。

[L] Liskov Substitution Principle(里氏替换原则):因为没有使用继承,所以不予讨论。

[I] Interface Segregation Principle(接口隔离原则):因为没有使用接口,所以不予讨论。

[D] Dependency Inversion Principle(依赖反转原则):有较好实现,三个主要的类内部的执行与另外两个类的内部执行没有依赖关系,依赖的是抽象的方法。

  • 第七次作业

2.3.1 代码规模

2.3.2 类图

2.3.3 类和方法复杂度

2.3.4 UML时序图

2.3.5 分析

这次作业虽然是多电梯,但其实在类的设计上与但电梯没有太大的差别,增加了一个调度类的原因是想避免单个调度器出现过于复杂的情况,然而上层调度器的算法依旧过于复杂。同时电梯类因为与第六次作业使用的几乎是相同的代码,复杂度依旧很高。增加了一个TransRe类用来包装拆分的请求。除了这些之外与第六次作业没有太大的改动。

2.3.6 SOLID原则

[S] Single Responsibility Principle (单一功能原则):没有实现,电梯类过于复杂,其中的许多方法的都具有多个功能。

[O] Open Close Principle(开闭原则):有较好实现,只需要改动主线程,就可以创建不同情况的电梯对象。

[L] Liskov Substitution Principle(里氏替换原则):因为没有使用继承,所以不予讨论。

[I] Interface Segregation Principle(接口隔离原则):因为没有使用接口,所以不予讨论。

[D] Dependency Inversion Principle(依赖反转原则):有较好实现,几个类内部的执行与另外的类的内部执行没有依赖关系,依赖的是抽象的方法。

三、分析自己程序的bug

  • 第五次程序在强测中没有出现bug,因为没有性能要求,所以是满分。
  • 第六次程序在强测中也没有出现bug,但是扣了一些性能分,看了强测的输出结果和自己的代码,我发现我的电梯在最后一个人出电梯之后没有办法把不同方向的请求捎带上,例如,当电梯下行至某一层,最后一位乘客出电梯了之后,如果这一层上有上行请求,电梯需要先关上门,再开门让这个上行请求的乘客上电梯,这样就多出了一次开关门的时间。这个bug我在第七次作业的时候修复了。主要原因还是因为电梯状态划分的不够明确,其实应该分为上行、下行和停止三个状态,一旦电梯为空,将状态设为停止,这样在查找捎带请求时就不会出现遗漏的情况。
  • 第七次作业的bug都是出在运行时间过长上面,都是电梯线程进入等待状态,从程序的运行上来看,应该是标志终止的全0请求没有被写入低层调度器,即应该是在换乘请求执行完之后没有请求再次写入,导致结束标志不能写入低层调度器,导致所有的电梯都处于等待状态。

四、心得体会

首先第一次接触多线程感觉还是很有意思的,虽然一开始不是很熟练,但熟悉了之后,一个一个线程思考其实和单线程差不多,主要会出现的问题就是共享数据和线程停止条件。

线程停止条件在第七次作业中可是让我吃了苦头,前两次的判断条件都因为换乘的请求不能使用,只好另辟蹊径,但之后又带来的一连串的问题,多线程的程序还不能进行断点的debug,只好自己思考可能是哪里出现了死锁。

再有一点,在这个单元中,我明显感觉到初期设计框架的重要性,因为考虑到后面可能出现的多电梯,所以我一开始的结构划分就比较明确,第六次和第七次作业都是在第五次的架构的基础上进行修改和添加,整体结构没有太大的变化,不需要进行重构真的是节省了很多时间。

BUAA_OO第二单元作业总结——多线程的更多相关文章

  1. OO第二单元作业总结【自我反思与审视】

    第二单元作业的完成史,就是一部心酸的血泪史…… 多线程的出现为我(们)打开一片广阔的天地,我也在这方天地摸爬滚打,不断成长!如果说第一单元之前还对Java语法有所了解的话,那么这单元学习多线程则完全是 ...

  2. BUAA_OO第一单元作业总结

    BUAA_OO第一单元作业总结 单元任务 第一单元的任务为实现表达式的求导,其中第一次作业是对简单多项式的求导,第二次作业是对包含简单幂函数和简单正余弦函数的多项式的求导,第三次作业是对包含简单幂函数 ...

  3. 【OO学习】OO第二单元作业总结

    OO第二单元作业总结 在第二单元作业中,我们通过多线程的手段实现了电梯调度,前两次作业是单电梯调度,第三次作业是多电梯调度.这个单元中的性能分要求是完成所有请求的时间最短,因此在简单实现电梯调度的基础 ...

  4. BUAA OO 2019 第二单元作业总结

    目录 总 架构 controller model view 优化算法 Look 算法 多种算法取优 预测未来 多线程 第五次作业 第六次作业 第七次作业 代码静态分析 UML 类图 类复杂度 类总代码 ...

  5. 电梯也能无为而治——oo第二单元作业总结

    oo第二单元作业总结 一.设计策略与质量分析 第一次作业 设计策略 在第一次作业之前,我首先确定了生产者--消费者模式的大体架构,即由输入线程(可与主线程合并)充当生产者,电梯线程充当消费者,二者不直 ...

  6. 【BUAA-OO】第二单元作业总结

    第二单元作业总结 ——电梯恐惧症患者的极限自救 一.   第一次作业程序分析 1.     设计策略简略分析 线程:主线程.输入线程和电梯线程,另有一个持有请求队列的调度器,一个对输入进行处理的Req ...

  7. BUAA_OO第二单元总结性博客作业——多线程电梯架构

    一.设计策略 在第一次作业时,我刚第一次接触多线程这个东西……于是乎对于第一次VIP直上直下一次只接一个人的电梯,我借鉴了指导书中为我们提供的架构,设计了一个输入线程和一个电梯线程,并设置了一个中间类 ...

  8. OO第二单元作业小结

    前言 转眼已是第九周,第二单元的电梯系列作业已经结束,终于体验了一番多线程电梯之旅. 第一次作业是单电梯的傻瓜调度,虽然是第一次写多线程,但在课程PPT的指引下,写起来还是非常容易:第二次作业是单电梯 ...

  9. 电梯模拟系统——BUAA OO第二单元作业总结

    需求分析 官方需求 本次作业需要模拟一个多线程实时多电梯系统,从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出. 本次作业电梯系统具有的功能为:上下行, ...

随机推荐

  1. hibernate一级缓存及对象的状态

    hibernate中实体类对象的状态 在hibernate中实体类对象有三种状态 (1)瞬时态(临时态) 瞬时态:即我们自己创建一个对象,还没有保存到数据库就叫临时态,其实也可以说是对像没有id值,跟 ...

  2. 【javascript】随机颜色

    调用该方法则会返回一个#xxx的rgb随机颜色 function color1(){ var sum=""; var shuzu2=['a','b','c','d','e','f' ...

  3. laravel 自动加载 自定义的文件/辅助函数

    需求 在 laravel 中自定义了一些 辅助函数,想要laravel框架自动加载这些函数 实现 将自定义的辅助函数放在helpers.php文件中,如下: 在compsoer.json 的 auto ...

  4. Python 计算当真因子个数为偶数个时为幸运数,计算区间内幸运数之和

    晚饭后朋友发来个问题,正好无事做,动手写了一下 若一个正整数有偶数个不同的真因子,则称该数为幸运数.如4含有2个真因子为 1 和 2 .故4是幸运数.求[2,100]之间的全部幸运数之和. 常规思路 ...

  5. Java中使用dom4j处理xml类型的文本

    在Java的实际开发中经常会碰到xml文件的解析,需要获取固定节点的内容,在这里简单记录一下,方便下次使用时查阅: 1.VSCode中安装插件XML Format,Shift + Alt + F撸直x ...

  6. .NET Core 2.0 项目的智能提示是英文,改成中文的解决方案

    截至目前为止(2017.09.21),Microsoft 官方并没有提供 .NET Core 2.0 正式版的多语言安装包. 因此,在 Visual Studio 2015 Update 3 和 Vi ...

  7. nodejs笔记之路由及util和url模块

    路由是URL到函数的映射:对于最简单的静态资源服务器,可以认为,所有URL的映射函数就是一个文件读取操作.对于动态资源,映射函数可能是一个数据库读取操作,也可能是进行一些数据的处理,等等. 如: /u ...

  8. github一些事

    我的个人github地址是:https://github.com/BUGDuYu 我们开发团队小组的github地址是: https://github.com/APPdoctrine 资源推荐: gi ...

  9. graph easy绘制ascii简易流程图

    graph-easy 日常我们经常需要画一些简易流程图,但是如果使用visio等工具来作图,一则略显大材小用,二则图片导出后再要粘贴.相比下,如果可以简单的用一些text的图来表达,则会简单的多.比如 ...

  10. ECMAScript课程

    ECMAScript的语法使用      https://blog.csdn.net/m0_37452696/article/details/78370504 ECMAScript 6简介说明   h ...