BUAA-oo-第二单元总结
2022/5/1 6:15:00
本文主要是介绍BUAA-oo-第二单元总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一、整体思路总结
要求分析
本单元的三次作业从简单到复杂,最终实现了一个具有横向和纵向两种电梯,具有换乘功能和动态增加电梯的电梯调度设计。
在第一次作业中,只有简单的纵向电梯,并且不支持动态增加电梯,最需要注意的是搭建好一个架构,并且注意线程安全问题。
第二次作业中,增加了横向电梯,支持动态增加电梯,但是仍然不支持换乘,乘客只能在同楼座或者同楼层移动。
第三次作业中加入了换乘以及定制电梯的属性。
具体设计
第一次作业
第一次作业最开始,我采用了比较简单的结构设计,因为感觉没有使用调度器的必要性,因此采取了一个列表类来作为中间不同线程的共享数据,并且内部实现方法来支持电梯对于列表的查询:
public class WaitQueue { private HashMap<Integer, RequestQueue> floorQueue; private boolean isEnd; }
其中RequestQueue
为单个的请求列表。
在这次作业中,整体的工作流程如下:
- 由
Main
创建WaitQueue
并创建电梯,运行电梯;创建输入类ReadThread
并运行。 - 输入类将请求送入
WaitQueue
中,电梯通过WaitQueue
提供的方法查询目前的请求的状态(当前层有同向请求,当前方向有请求,当前层有反向请求,反方向层有请求),并结合自身的状态(电梯内是否有人)来决定自己的行动。 - 当电梯收到
WaitQueue
传回的无请求的信息后,结束运行。
判断结束条件为该楼座的列表为空并且输入结束。
第二次作业
第二次作业与第一次作业差距不大,主要的变化有以下几点:
- 输入类
ReadThread
可以根据输入新增并运行电梯。 - 新增了一个横向电梯类
ElevatorFloor
,采用的是绕一个方向循环的策略。 - 由于针对多部电梯采用了自由竞争的策略,而我的查询是否有请求和取请求的操作不在一个锁内,因此每次取请求都要判断是否能取到,防止出错。
第三次作业
第三次作业与前两次差距较大,主要是因为换乘的加入,结束控制和一些细节方面都需要有较大的变化,因此我在这次作业中的结构设计与前两次区别较大,主要体现在以下几点:
- 首先是实现了一个
Controller
负责整体的管控,控制结束以及管理所有电梯的队列:
public class Controller { public static final Controller CONTROLLER = new Controller(); private int count; private ArrayList<WaitQueue> waitQueues; private boolean inPutEnd; private boolean endTag; }
这里展示了Controller
的整体结构,定义了一个全局使用的CONTROLLER
,可以通过getInstance
方法来获得,定义count
、inPutEnd
、endTag
三个变量用来控制结束,通过对输入请求和结束请求的计数来判断是否该结束。
同时,将getPerson和addPerson的功能全部整合到了Controller
里面,全局调用该方法即可。
- 然后是利用
MyMap
类来帮助每一个人规划路线,在请求进入的时候就规划好路线,具体的调度会在后面详细展开。 - 设计了自己的
Person
实现类来更好的实现调度功能。
二、第一次作业
2.1同步块设置和锁
在这次作业中,为了保证线程安全,主要在以下几个地方上锁:
- 输入线程、所有电梯线程的共享访问对象
WaitQueue
以及构成它的RequestQueue
类中的所有查询、删除、改动的方法。 - 由于官方输出包的线程不安全,新建了输出类
OutPut
类,并在其中对输出上锁。
同步块的设计:
本次作业中的线程主要就有输入线程以及所有的电梯线程,它们通过列表WaitQueue
进行交互,互不干扰。
2.2调度器设计
本次作业我采用Look算法,主要有以下几点:
- 在
WaitQueue
中有方法findNextPerson
来告诉电梯现在列表里面的请求情况,包括以下四种:当前层有同向请求、当前方向有请求、当前层有反向请求、反方向层有请求。通过findNextPerson
返回的信息以及自身队列里面是否有乘客要下电梯来判断自己是否应该开门(开门的情况:里面有人要下|外面有人要上),是否转向等等。 - 电梯以自身内部乘客的运行方向(自身现在的运行方向)为第一优先级,只会接同向的乘客,如果没有同向的请求,电梯转换方向,接取乘客。
2.3类图和时序图
类图
时序图
2.4bug分析
本次作业因为一开始没有认识到输出线程不安全,因此互测的时候被hack了,后面自己封装了线程安全的输出类后解决了问题。
三、第二次作业
3.1同步块设置和锁
同步块和锁的设置几乎与第一次作业相比没有变化。
3.2调度器设计
首先横向电梯方面,采取了简单的单向循环的策略,电梯在ABCDEA这个方向上不断移动,本座有要下的乘客或者要上的(所有在本座的乘客都可以上)就开门,只要通过WaitQueue
查询到列表不为空就继续移动。
纵向电梯方面,单个电梯的策略直接采用了上次的Look算法;多电梯调度方面,直接采用了自由竞争,多部电梯可同时查询到同一个乘客并出发去接,抢到乘客的就继续,没抢到的继续搜寻列表,通过每次对取请求的结果判断是否为Null
来避免bug。
3.3类图和时序图
类图:
时序图:
3.4bug分析
本次作业在互测和公测中均没有发现bug。
四、第三次作业
4.1同步块设置和锁
锁的设置:
本次作业对Controller
的所有方法以及RequestQueue
和WaitQueue
的所有方法上锁,对输出线程封装线程安全的输出方法。
同步块:
本次作业有输入线程和两种电梯的线程,输入线程由Main
来启动,电梯线程由Controller
来启动,交互通过Controller
控制的WaitQueue
来进行,两种电梯的队列是分开的。
4.2调度器设计
本次作业的调度主要由以下几步:
- 输入一个请求后,由
MyMap
类为该请求规划路线(实际上是规划了一个换乘的楼层),规划的函数如下:
public int makeChangeFloor(PersonRequest personRequest) { int fromFloor = personRequest.getFromFloor(); int toFloor = personRequest.getToFloor(); char fromBuilding = personRequest.getFromBuilding(); char toBuilding = personRequest.getToBuilding(); int changeFloor = -1; for (int i = 1; i < mapElevators.size(); i++) { int tag = mapElevators.get(i).canBeUsed(fromBuilding, toBuilding); if (tag != -1) { if (tag == fromFloor) { changeFloor = fromFloor; return changeFloor; } else if (tag == toFloor) { changeFloor = toFloor; return changeFloor; } else if ((tag > fromFloor & tag < toFloor) | (tag < fromFloor & tag > toFloor)) { changeFloor = tag; } else if (changeFloor == -1) { changeFloor = tag; } } } if (changeFloor == -1) { changeFloor = 1; } return changeFloor; }
其中,mapElevators
储存了所有横向电梯的可停留楼座、所在层等信息,用来规划路线,规划路线的逻辑如下:
如果(请求所在楼座==请求所要去的座),直接将换乘楼层赋予为现在的楼层,后续根据电梯接人的逻辑,该请求会直接被纵向电梯接走。
接下来就是分配该请求所需的横向电梯:
以请求出发楼层、所前往楼层的横向电梯为第一优先级;
以请求出发楼层到所前往楼层的横向电梯为第二优先级;
以1层横向电梯为第三优先级;
为请求分配换乘楼层;
2.由Person
的属性:
public class Person { private int id; private int toFloor; private char toBuilding; private int nowFloor; private char nowBuilding; private int nextToFloor; private char nextToBuilding; private int exchangeFloor; private int state; //1为向上,2为向下,3为横向,4为已经到达目的地 }
人通过目前的状态和属性,为自己规划nextToFloor
和nextToBuilding
两个属性:
public void changeState() { if (nowBuilding == toBuilding & nowFloor == toFloor) { state = 4; } else if (toBuilding == nowBuilding) { nextToFloor = toFloor; if (nextToFloor > nowFloor) { state = 1; } else { state = 2; } } else if (exchangeFloor != nowFloor & toBuilding != nowBuilding) { nextToFloor = exchangeFloor; nextToBuilding = toBuilding; if (nextToFloor > nowFloor) { state = 1; } else { state = 2; } } else if (exchangeFloor == nowFloor & toBuilding != nowBuilding) { state = 3; nextToBuilding = toBuilding; nextToFloor = toFloor; } }
每次在上述属性有变化的时候调用该方法。
3.电梯通过人的nextToFloor
和nextToBuilding
两个属性来判断是否需要接走该乘客,基准的策略与前面两次作业相同,采用横向循环加自由竞争、纵向Look加自由竞争的方式进行,注意横向电梯需要判断是否可达。
4.3类图和时序图
类图:
时序图:
4.4bug分析
本次公测被测出一个bug,我在规划路线和判断是否有请求的时候都判断了横向电梯的可停靠问题,可是在电梯从队列里接人的时候忘记判断,导致有可能有人上了他不该上的电梯,导致横向电梯一直循环,最后导致了rtle。
五、发现bug所用的策略
本次互测中,我虽然下载了很多他人的代码并且仔细阅读,但是并没有发现什么Bug,除了第一次很多人输出线程不安全之外,没有发现新的bug。
因为多线程运行的复杂性,与第一单元相比,bug的判断更加困难了。
六、心得体会
本单元的作业是我第一次接触多线程的编程,在助教们精心设计的题目和通过实验提供给我们的代码的帮助下,让我初步了解了多线程编程的思想与一些方法,我受益良多。
在本单元的第一次作业开始前,我阅读了《图解Java多线程设计模式》中的基础部分和生产者-消费者的相关部分,了解了多线程需要用到的基础的语法和相关的一些具体的实现方法。
之后的架构设计借鉴了讨论区的同学们和实验代码。
第三次作业第一版尝试了一下优化性能,结果因为有些复杂导致出现了不能de出来的bug,后来临时重构了代码,换了另一种思路,调度器Controller
的思路是从实验代码中借鉴的。
通过这几次电梯的实战,让我认识到多线程的第一优先任务是保证线程安全,对共享的对象的相关方法利用关键字上锁。同时让我体验到了多线程debug的不友好,由于断点调试在一定程度上不能使用,最开始写代码时就要保证逻辑的完备性是很重要的。
同时因为第三次作业的重构,让我认识到:有些时候自以为的优化其实可能带来很多麻烦!!!
这篇关于BUAA-oo-第二单元总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign学习入门:轻松掌握微服务通信
- 2024-12-27OpenFeign学习入门:轻松掌握微服务间的HTTP请求
- 2024-12-27JDK17新特性学习入门:简洁教程带你轻松上手
- 2024-12-27JMeter传递token学习入门教程
- 2024-12-27JMeter压测学习入门指南
- 2024-12-27JWT单点登录学习入门指南
- 2024-12-27JWT单点登录原理学习入门
- 2024-12-27JWT单点登录原理学习入门