第二单元:多线程实时电梯系统

一.写在前面

第二单元目标是通过模拟多线程实时电梯系统,熟悉线程的创建、运行等基本操作,熟悉多线程程序的设计方法。这个单元的学习,对于多线程一些基本语法,例如synchronized,wait(),notify(),notifyall()之间的联系和作用会有很深的理解。其次,通过数据结构以及相应的的最短路径算法求出电梯运行的合适方法。同时,对于现实生活中的电梯系统,我们可以通过这次机会,利用自身专业提供一些更高效,省电的运载策略模式,真正做到学以致用。

二.任务要求

整体要求

本次作业需要模拟一个多线程实时电梯系统,该电梯系统支持动态扩展和日常维护,同时需要支持更加高级的调度功能。

​ 系统基于一个类似北京航空航天大学新主楼的大楼,电梯可以在楼座内 1-−11 层之间运行。

​ 系统从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出。

​ 具体而言,本次作业电梯系统具有的功能为:上下行,开关门,以及模拟乘客的进出,以及模拟电梯系统扩建和日常维护时乘客的调度。

​ 电梯系统可以采用任意的调度策略,即在任意时刻,系统选择上下行动,是否在某层开关门,都可自定义,只要保证在电梯系统时间不超过系统时间上限的前提下将所有的乘客送至目的地即可。

​ 电梯每上下运行一层、开关门的时间为固定值,仅在开关门窗口时间内允许乘客进出。

​ 电梯系统默认初始在 11层有六部电梯,这些电梯均可达所有楼层。

第一次作业

要求实现一个六部电梯协同运作的系统。

第二次作业

在前面系统的基础上,模拟电梯系统扩建和日常维护时乘客的调度。实现电梯数量的动态分配。

第三次作业

在前面的基础上,支持电梯定制化功能,即一部电梯不一定能在所有楼层停靠(但显然可以经过所有楼层),同时需要支持对同一楼层内同时存在的停靠电梯数量的控制。

三.设计思路

总体思路

在理解生产者消费者模式,借鉴了往届学长的博客后,明确了大致的框架:通过inputThread这个线程类(生产者)将产生的Person(想要乘坐电梯的乘客)放入PersonQueue容器中,再通过Controller这个调度器(消费者)将容器中的乘客分配给六个电梯,电梯采用相应策略调度后进行输出。

架构分析

在这里插入图片描述
架构中总共有inputThread,Controller,(ElevatorProcess1~6) 8个线程。Controller通过分配生产者产生的乘客去启动6个电梯线程,笔者采用的分配策略如下:首先利用Floyd法算出乘客到达目的地所需要的最少电梯数,然后根据其路径进行匹配,接着哪个能匹配到的电梯离待分配乘客较近就选择哪个电梯(当然前提是电梯未满),然后将Person存入选择的电梯线程中的缓冲池(同样也是利用PersonQueue容器)进行调度。 进入某个电梯的缓冲池后,电梯采用look算法接人,具体就是每经过一层楼判断一次,如果电梯没人则选择最近的乘客,如果有人则接同方向且在该楼层的人。(电梯中的乘客也是用PersonQueue容器装入) 最后调度后利用output类输出。

总体框架

在这里插入图片描述

Person

电梯中乘客,将其作为一个类,封装它的一些属性,比如乘客id,所在楼层,当前一条路径下的楼层,最终目标楼层。部分代码:
在这里插入图片描述

PersonQueue

乘客队列,用来存储乘客的容器,主体属性是ArrayList,将其封装起来是因为线程安全问题,在需要的方法上加上synchronized保证线程安全。其中isEnd属性判断这个容器是不是停止装入了。部分代码如下:
在这里插入图片描述

Elevator

电梯类,用来封装电梯的一些性质,比如电梯的id,运行速度,开关门的所需时间,容量,起始楼层和可停靠楼层等。
在这里插入图片描述

Pickup

对于支持对同一楼层内同时存在的停靠电梯数量的控制,题目中的描述的对任意楼层X,处于服务中的电梯的最大电梯数量Mx = 4,处于服务中的只接人的电梯的最大数量Nx = 2。
Pickup用来限制只接人的电梯数量,当有电梯需要在这一楼层接人的时候,需要判断是否此楼层已有两部电梯处于接人状态,若有则等待其接完人再继续。同时电梯接人需要将接人电梯数++。示例代码如下:
在这里插入图片描述

Server

Server类用来限制处于服务中的最大电梯数量,思路与Pickup一致。
在这里插入图片描述

ElevatorProcess

电梯运行的线程类,每个电梯通过一定策略模式将乘客运到指定楼层。运行策略的话大致采用look法,上述有提到。
在这里插入图片描述

ElevatorQueue

电梯队列,用来存放电梯的容器,主体是ArrayList ,将其封装起来也是因为线程安全问题,在需要的方法上加上synchronized保证线程安全。
在这里插入图片描述

MainClass

主函数主要进行生产者线程InputThread和消费者线程Controller的创建,这样就能进行产品(这里指电梯的乘客)的生产(乘客输入请求想要乘坐电梯)和消费(将乘客运到指定地点)。部分代码如下。
![在这里插入图片描述](https://img-blog.csdnimg.cn/119d52101c054a9e84e4ddbaa5893a94.png

主函数还进行最开始的六部电梯的初始化创建,每个电梯都设置自己的请求队列,InputThread产生的乘客经过Controller调度后放进各个电梯的请求池中让各个电梯工作,分工合作逻辑更清晰。部分代码如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/83bf2a25931649e2916a14fb4fc86779.png

InputThread

统一采用resquest输入,再判断是乘客请求,电梯请求还是维修请求,利用多态进行相应的处理。
一来用来生产乘客,将所产生的乘客存入resquestQueue这个缓冲池中,将产生的新增电梯存入elevatorQueue这个电梯队列中。部分代码如下:
在这里插入图片描述
二来用来新增电梯,获取电梯的属性后,将新增的电梯放入elevatorQueue电梯队列中
在这里插入图片描述
三来用来接收维修请求,将其传入相应电梯,保证电梯接到请求放出乘客进行维修:
在这里插入图片描述

Controller

调度器Controller负责将缓冲池中的乘客放进各个电梯的请求池中处理(算出最短路径找到匹配电梯),至于放置策略每个人都有自己不同的方法,笔者能力有限,只进行简单的分配,即有限分配没人且停靠楼层靠近乘客所在楼层的电梯,若电梯都有人,则哪个电梯离乘客进则放到哪个电梯的请求池中。其中最短路径算法如下:
在这里插入图片描述

Output

各个输出所需格式都在这里封装。部分代码如下:
在这里插入图片描述

架构过程

第一次作业

架构特别重要,所以在编写前我尽可能利用这门课的核心思想–面向对象去构思,最后得出一个拓展性不差的一个框架,各个类都被较好的封装,各司其职而且线程安全问题得到了保证。在第一次作业时候大体架构就是这样,后面两次迭代的时候我并未对其进行修改,只是按照要求增量进行一些完善。

第二次作业

增加了电梯系统扩建和日常维护时乘客的调度。
对于电梯的动态扩建,只需要在输入线程中增加一个电梯的输入,保证线程安全的前提下将新增的电梯放入电梯队列lelevatorQueue中即可。往后乘客分配过程中就会自然调度新增的电梯。
对于日常维护,这就是电梯运行过程中的问题,在输入线程中收到电梯需要维护的信号,将其通知到对应的电梯,电梯运行中收到这个信号,再运行到下一楼层的时候把乘客全部放出来(就是放到请求池中)让其他电梯来接送。

第三次作业

对于电梯定制化功能,相当于给了调度很大的限制,因为电梯不能每个楼层都停靠,此时分配电梯就很像求最短路径问题,因此将每层楼看作一个点,电梯可达的楼层之间有路径,即可求出最短路径,继而找到匹配的电梯,满足定制化。对于支持对同一楼层内同时存在的停靠电梯数量的控制,只要在电梯停靠在该楼层时进行相应的统计工作,超过对应的数量进行相应的等待即可。
至此,所有问题都有明确思路和解决方法。

架构优缺点分析

优点:电梯系统中所有的对象笔者都进行了很好的封装,并且各司其职,即使有交互都保证了线程安全。而且,如果有新的要求加入,因此封装完善,拓展性也不差,因此按照请求进行相应增加能保证架构的不变性。
缺点:线程安全问题虽然得到了保证,但是处理的比较杂乱,到处都是,再进行拓展下去可能会出现死锁,轮询等问题。

锁的选择

锁本质上是为了线程安全问题,若有两个线程都需要用到同一对象且需要修改时会产生延迟错误,在这个结构需要变化的对象就是PersonQueue(乘客队列),ELevatorQueue(电梯队列),Pickup(接人数量),Server(服务数量),我将锁都封装到了类里面,这样会比较清晰一点,统一对对象采用synchronized(),当需要用到对象里面的东西时,就需要占住这把锁,此时其他线程再想访问或修改就需要等待,不会产生问题,但是这样也会影响效率,我在第二次作业加了比较多的锁,导致效率过低,在第三次作业里保证线程安全的前提下进行了适量的删除。

性能要求

我利用最短路径选择了最少的电梯,此时消耗的电梯电量就比较少,而调度策略我采用了优先分配给空电梯,若空电梯数量多余,则哪个电梯离的近优先接人,look()策略也会让时间减少。但是这肯定不是最优策略。笔者能力有限,其他方法还未想到。

四.代码复杂度分析

代码规模

在这里插入图片描述
代码较长,每个过程都写的比较具体,重心放在电梯处理和策略调度上

类的度量分析

在这里插入图片描述
输入线程,调度器等平均复杂度都比较高,可以在一些复杂的实现中再创建新的类实现。

方法的度量分析

在这里插入图片描述
方法度量也是集中在输入线程和调度器,一个方法写的过于冗长。

五.bug分析及hack策略

第一次作业

第一次作业强测得分95.6983,互测没有被找出问题,也没有找出其他人的问题,整个房间hack数也就1,大家的电梯系统都挺完善,找不到较大的问题。
我的hack思路都是从刁钻的出发:
第一次作业只有六部电梯,每部电梯限乘6人,因此我在同一时间安排36个乘客,判断这个电梯系统是否能相应所有乘客不出问题。
接下来我就再安排一名乘客,判断所有电梯都满员的情况下会不会继续接纳乘客。

第二次作业

第二次作业强测得分89.8651,没有很多时间去优化策略,所以性能分比较差。互测刀中了3🔪,被🔪了4次。
被🔪中的四次是有两个问题,一个是电梯维修过程中需要从电梯队列中删除电梯,此过程没有加锁导致了线程安全问题。另一个是因为策略中我选择哪个电梯离得近用哪个电梯接,计算乘客和电梯距离忘记加绝对值,有一定概率死循环。
我对于新的要求的hack思路,大致按以下几种方式:
新增电梯后是否继续增加大量请求(超过原来电梯所能接纳的乘客数),判断新电梯会不会接纳乘客,若没有接纳可能会产生超时情况。
对于维修请求判断接下里两个arrive里有没有相应,响应之后电梯送去维修再增加请求是否维修的电梯还会继续接纳乘客。

第三次作业

第三次作业强测得分90.3721 分,强测错了一个点,互测没有被🔪中,🔪了别人一次。
强测主要问题是:在计算接人的数量的时候没有考虑线程安全问题,导致同时有电梯在同一楼层接人时同时满足条件开门超过数量。
对于新的要求,我的hack思路大致如下:
判断电梯是否停在了不该停的地方。
判断乘客乘坐的路径和换乘有无问题。
判断服务中和接人的数量有没有超过最大的限制。

六.心得体会

本单元相对于第一单元花的时间少了特别多,架构也清晰了许多,对于层次化的设计没有那么头疼,或许是适应了课程的节奏,或许是逐渐对面向对象有了更深的认识,但是其中细节的处理还是让笔者特别头疼,特别是线程安全问题,一旦各个线程共用的对象多了起来,那么就会因为线程安全出现更多的bug,需要花更多的气力去解决,当然本单元的主要内容是线程安全,因此这是不可避免的问题。对于这三次作业之间的比较,第二次作业最简单,第一次作业因为是要架构,所以稍微花的时间稍长,第三次作业最令我头疼,电梯换乘的策略和最短路径之间的联系想了特别久,最后经过一系列失败的尝试和同学的帮助才得以成功。总的来说,革命尚未成功,同志还须努力。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值