前言

本单元作业主要以设计电梯来实现多线程编程。本章主要学习了如何使用多线程以及如何确保多线程安全,从电梯的调度策略中学会了如何简单地使用synchronized锁来控制线程安全。

首先,明确锁的两个常用的用法:synchronized修饰一个方法,synchronized修饰一个类。前者作用范围是整个方法,作用的对象是调用这个方法的对象。后者作用于关键词后大括号圈定的代码段,作用对象是这个类的所有对象。之后,便是自己代码的分析。

一、程序结构分析,

第一次作业

1)设计思路

第一次电梯作业实现的是一个简单的可携带的电梯,此时的电梯没有人数限制,并且只有一部电梯,对于要求范围的目的地都可达。第一次作业,具体难度在于,如何理解电梯的调度策略,并确保线程安全。第一次作业要预留足够的迭代空间,就得要考虑一些比较通用的问题。

首先,采用什么设计模式。因为第一周我们就学习了生成者-消费者模式,所以笔者就采用了这一种模式。这里并没有采用指导书的建议,将主线程设计为生成者线程,因为考虑到主线程的功能应该是简单地线程启动,以后迭代时,还可能会添加新的进程,所以单独设计了一个生产者线程。这里调度器(托盘),不作为单独线程,而是设置成一个请求队列,并且带有put,delete等方法。电梯作为消费者,不断的去访问队列,如果完成了一次就将队列中相应请求删去。总体的思路便是这样的一种生成者-消费者模式。

其次,调度策略的选择。整个单元的调度策略,我都选择了最容易实现的ALS策略,并且在经过每一楼层时都判断是否可以上下人,运行方向主要是根据主请求来确定的。主请求的选择,采用了纯贪心的思想,即,在当前楼层下,检索队列,此时再细分为电梯有人:选取最早到达为主请求。电梯没人:选取最近可以接到的请求为主请求。这里又涉及到了,电梯有没有人的判断,所以电梯类加入一个成员变量,考虑到以后的迭代问题(限定电梯内的人数),我们这里加入了一个people变量表示电梯内的人数。所以整个电梯类成员如下:

   private Dispatch dispatch;//绑定队列
private int currenfloor;//记录当前楼层
private PersonRequestPlus mainrequest;//主请求
private int people;//电梯内人数
private int dir;//方向 public Elevator(Dispatch dispatch) {//构造方法
this.dispatch = dispatch;
currenfloor = 1;
dir = 0;//0静止,1向上,2向下
people = 0;
}

PersonRequestPlus是用原有类进行扩展得到的,用途是加入了其他需要表示乘客状态的变量,如in(是否进入电梯)等。

此外,对于本单元作业,还有一个重要的点就是如何结束进程。

对于生成者线程,当输入NULL的时候,就表示生成者线程的结束。但是对于消费者线程而言,不再有请求加入不代表电梯就要停下,此时应该要完成之前所有请求才可以停下。因为我们完成一个请求就会将其从队列中删除,所以当所有请求都完成时,调度器里的队列应该为空。据此,在调度器设置两个布尔标志,Dead和Empty。对应四种情况:

empty

True

Flase

dead

Ture

整个线程结束

线程继续,不再有新请求,电梯继续工作

False

电梯悬停,等待请求

正常情况

在线程安全方面,我对应的只有一个生成者和一个消费者,对put和delecte加锁即可。同时,因为调度器和队列重合使用,所以在消费者中,对请求队列进行循环查找时,要对调度器加锁(避免遍历的时候加入或者去掉了某个元素,造成错误)。类图如下(省去具体成员):

2)基于度量分析

首先是代码长度:

因为本单元作业有标准的生产者消费者模板,所以本次作业有面向对象的样子。每一个类各司其职,每一个类的行数都不是很多(电梯类主要模拟了电梯的运动,所以较长)。实际上为了更好的迭代,电梯类可以接着拆分,一个ElevatorBase类表示电梯的基本运动,例如上下行,开关门等模拟等。还有一个类,专门实现电梯类的调度方式,这样下一次迭代时,原来的基本运动不用更改,只需更换策略即可。

总体来说,没有很高的复杂度,实现了高内聚低耦合。

依赖分析上来看,没有出现红块,说明整体结构还是比较合理的。

第二次作业

1)设计思路

第二次作业相对第一次作业而言,主要是多电梯的考虑、楼层数量的增加,以及电梯人数的限制。电梯人数由于上次预留了空间,所以这次只要限制一个people的最大值即可。楼层数量增多问题不是很大,主要是注意到不存在0楼这个概念(即-1上一层为1)。所以本次更多需要考虑的是多部电梯的协调。本次作业,笔者为了安全起见,并没更改调度器,即还是采取调度器为请求队列,调度器不对请求进行分配,而是电梯进程启动后,由电梯自己去进行抢夺。并且为了不混乱请求队列,每一个请求再添加一个标志,表示属于哪一个电梯,上电梯时标记请求,出电梯时根据遍历同一个请求队列,根据已有的标志识别是否属于本电梯进行剔除。这种方案在请求越多时,由于自行抢夺,最终电梯所在楼层各不相同,新请求加入时,离其最近的会优先抢到,所以总体效率不会太低。但是不得不承认,这种调度策略虽然简单,但是现实中不会这么实现的,电梯会多出许多空跑,浪费电。基于本次建构想到的一个改进思路就是,电梯自己先模拟抢的过程,最后总会有一个最快抢到的电梯,这个电梯动,其他电梯不动。

该次作业,多电梯的出现,并且是采用自行抢夺的问题,那么线程安全上,最大的问题就是多电梯对公共队列的访问以及改写,所以对Dispatch类(调度器)进行加锁处理。这样很大程度上解决了线程不安全的问题。类图如下:

2)基于度量分析

首先是代码的长度

代码长度上几乎不变,本次作业采用了比较简单的调度策略,所以在上一次作业的基础上改动并不是很多,例如调度器Dispatch就没有动过,Producer也几乎没有改变。

这里复杂度较高的是inOrOut方法,其他复杂度不高。该方法复杂的原因是本人的进出都是遍历队列查找的,在这个时候out操作就是删除队列,如果不是线程安全的容器,就会报错,所以本人每一次删除都退出循环重新遍历,即双重循环,通过设置一个flag来退出大循环。

依赖程度与上一致,无较大变化。低耦合高内聚实现良好。

第三次作业

1)设计思路

第三次作业,是在第二次作业基础上的迭代,主要添加了几个难点:动态添加电梯,电梯电梯停靠楼层限定,以及不同种类电梯的运行参数。动态添加电梯,对于第二次作业启动电梯自行抢占的方式来说,只需确保线程安全即可;运行参数的限定,只要在电梯类加入新参数最后再根据不同的类型,在构造函数中赋予不同的初始值;最后,重点在于电梯停靠楼层的限定,限定问题就涉及停靠楼层(添加一个是否能够停靠的函数)该限定就增加了另一个需要考虑的问题——换乘问题。

本次作业重点需要解决的问题就是换乘问题。根据第一次作业的设想,电梯类实际上功能为两部分,一部分是基础运动,一部分是调度策略。所以第三次作业的换乘问题,本身上只需要改动调度策略。这里笔者采用最简单换乘方法,一个用户上电梯,先判断是否要换乘(添加一个是否能够直达的函数,即from和to的楼层都可以停靠),需要换乘,则将请求拆分,这就需要一个换乘地tem,使得(from,tem)对当前电梯可以直达,(tem,to)对其他类型电梯可直达。于是就改为两个请求,其中tem的选择优先考虑from和to直接的楼层,之后再考虑离二者最近的位置向-3和20散开。

类图如下:

类图基本与前两次作业一致,只是添加了一些功能。

2)基于度量分析

首先是代码长度:

三次作业代码行数299->357->482。三次迭代,代码稳定上升。但是第三次作业,明显电梯类有点臃肿,因为模拟电梯和调度策略都放在一个电梯类的中来实现,也就是之前说的,电梯类可以拆分成两个类。其他部分只是为了完成目标进行的适当完善。

本次作业的复杂度,在电梯类中,因为大部分的功能实现都在其中实现,所以电梯类中的许多方法的复杂度出现了红块。所以第三次作业的耦合度较高,不是很好的编程。

在类之间的依赖度分析上来看,与之前的几乎一致,没有很大的变化。

三次作业基本上使用该时序来完成作业,第三次作业特殊的地方是Produce额外产生了电梯进程。

3)设计原则角度分析

二、作业出现的bug

第一次作业

公测阶段:在强测中未发现bug,但是提交过程中出现了线程不安全的情况,主要原因是在电梯类对请求队列遍历时,生产者又产生新的请求加入队列,所以每一次遍历都加上锁。

互测阶段:未发现bug。

第二次作业

公测阶段:在强测阶段未发现bug,提交过程中经历了cputle的情况。主要原因是采用了电梯抢夺的策略,对于多电梯运作造成cpu过于繁忙。这里采用了某大佬推荐的sleep(1)的方法,每次抢之前先休息一会儿,如果抢不到,这个时候就会告诉cpu,就不占用cpu资源。

互测阶段:未发现bug。

第三次作业

公测阶段:在强测阶段未发现bug,提交过程主要是换乘的时候,由于是类打表操作,所以出现了未剔除0楼的现象,使得出现了许多bug(WA,RTLE都出现过)。

互测阶段:未发现bug。

三、分析别人程序bug策略

第一次作业:互测hack到了1次。采用的策略就是随机生成,所有数据都是手动随机构造(即自己随便写的),因为线程安全的问题有时候不是一定出现的,所以随机策略,能中就中。不过中的那一刀用的采用了时间上的特殊性,因为我们在开关门时间内可以进人,所以设想一个电梯到达了一个楼层进行开关门,这个时候加入新请求,看看会不会出现问题,果不其然就中了一刀。

第二次作业:相安无事。主要采用的是同时加入大量数据进行测试来测量有没有超容量的问题。之后还是采用随机的策略,进行随机打击。

第三次作业:构造大量需要换乘的数据并且在同一时间加入,这是一批数据。其次改变策略,想看看是否会出现提前结束的问题,例如按照自己的换乘策略,一个请求要换乘,就得把一个策略拆分成两个,即产生新的并删掉旧的。所以,如果是先删后加并且不是原子操作,很可能在某一时间出现了请求队列为空,电梯死亡。

四、心得体会

本单元作业的结束意味着课程已经过了一半了。自己最大的心得就是自己能力不断提高。尤其第一次作业时,完全理不清楚多线程的运行机理,到现在可以自己分析处自己线程不安全的地方,这里是花费了很多夜晚(头发)的。

还有就是本单元作业,因为有了比较标准的模式供我们参考,所以整体来说,很有面向对象的那个味了,特别是作业的迭代过程,已经没有上单元那样的大规模重构了,整体框架延用三周(也不罔顾了我第一周花了好久的时间才想清楚的结构)。而且代码风格上,也尽量的达到了高内聚低耦合的程度,算是比较大的突破。

还有值得自己比较庆幸的是,自己由于第一次作业的架构思路简单明了,出现wait的情况有且只有队列为空但输入未结束时,而notifyall只出现在添加请求上,所以,第二三次作业迭代的时候并没有出现因为胡乱加锁而出现的死锁问题。

但是很遗憾的是,自己由于第一次作业拖得时间比较长,没有考虑更好的想法,以及第二次写多电梯的时候,没有考虑分配请求。使得二三次作业,电梯采用了抢夺请求的方式,这种方式虽然时间上不至于超时,但是也有一定的功能损耗,虽然减少了出现bug的可能性,但是自己在多线程这一块没有得到更深一步的强化训练。同时,这一电梯策略非常不符合实际,有点应付题目的感觉。

最后,希望下一单元的挑战自己也能笑着面对(活着就好)。

OO第二单元——电梯作业总结的更多相关文章

  1. OO第二单元电梯作业总结

    目录 目录一.第一次作业分析设计策略基于度量分析程序结构二.第二次作业分析设计策略基于度量分析程序结构三.第三次作业分析设计策略基于度量分析程序结构四.分析自己程序的bug五.发现别人程序bug所采用 ...

  2. OO第二单元电梯线程系列总结作业

    电梯系列第一次作业 功能描述: 傻瓜电梯无需考虑超载捎带 线程模式: Producer-Consumer Pattern 思路: 第一次作业是一个傻瓜电梯,分别有一个生产者生成电梯指令(也就是Inpu ...

  3. OO第二单元(电梯)单元总结

    OO第一单元(求导)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉理解和掌握多线程的思想和方法.这个单元以电梯为主题,从一开始的最简单的单部傻瓜调度(FAFS)电梯到最后的多部 ...

  4. 北航OO第二单元——电梯调度

    三次作业要求简介 特点:目的选层电梯 在电梯的每层入口,都有一个输入装置,让每个乘客输入自己的目的楼层.电梯基于这样的一个目的地选择系统进行调度,将乘客运送到指定的目标楼层. 第一次: 在任意时刻输入 ...

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

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

  6. oo第二单元作业总结

    oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...

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

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

  8. OO第二单元多线程电梯总结

    OO第二单元多线程电梯总结 第一次作业 设计思路 Input为输入线程,负责不断读取请求并将读到的请求放入调度器中. Dispatcher为调度器,是Input线程和Elevator线程的共享对象,采 ...

  9. OO第二单元——多线程(电梯)

    OO第二单元--多线程(电梯) 综述 第二单元的三次联系作业都写电梯,要求逐步提高,对于多线程的掌握也进一步加深.本次作业全部都给出了输入输出文件,也就避免了正则表达式判断输入输出是否合法的问题. 第 ...

随机推荐

  1. NGK又双叒叕送钱了!百万SPC空投不要错过!

    不知不觉,2021年已然到来.回顾过去一年,2020年币圈发生的事情真的是太多太多,比特币的持续暴涨,DeFi一波又一波的空投福利,都让我们见识了区块链的魅力!同样,2021年区块链市场的牛市仍然持续 ...

  2. YFI币之后,BGV能否主宰DeFi 沉浮?

    回望今年,币圈风起云涌,比特币.YFI.BGV等一众数字货币共同打造了火热的币圈景象,在短短的时间里可以说是又形成了新的生态,业内对于BGV等新币种的认可度也达到了新高.2020已经接近尾声,放眼20 ...

  3. go-admin在线开发平台学习-2[程序结构分析]

    紧接着上一篇,本文我们对go-admin下载后的源码进行分析. 首先对项目所使用的第三方库进行分析,了解作者使用的库是否是通用的官方库可以有助于我们更快地阅读程序.接着对项目的main()方法进行分析 ...

  4. 算法图解:Python笔记代码

    二分查找 选择排序 递归 快速排序 广度优先搜索 狄克斯特拉算法 贪婪算法 二分查找 def binary_search(lst,item): low = 0 high = len(lst)-1 wh ...

  5. Elasticsearch 分片集群原理、搭建、与SpringBoot整合

    单机es可以用,没毛病,但是有一点我们需要去注意,就是高可用是需要关注的,一般我们可以把es搭建成集群,2台以上就能成为es集群了.集群不仅可以实现高可用,也能实现海量数据存储的横向扩展. 新的阅读体 ...

  6. 从Java的堆栈到Equals和==的比较

    以下为链接 https://www.2cto.com/kf/201503/383832.html 栈与堆都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地 ...

  7. KnowRbao_uni-app

    uni-app开发项目模板 主要的代码如下: pages.json 这里是添加页面的路径代码还可以设置标题: { "pages" : [ //pages数组中第一项表示应用启动页, ...

  8. SpringBoot整合MyBatis-Plus框架(代码生成器)

    MyBatis-Plus的简介 Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发.提高效率而生. 代码生成器 通用的CU ...

  9. POJ-3159(差分约束+Dijikstra算法+Vector优化+向前星优化+java快速输入输出)

    Candies POJ-3159 这里是图论的一个应用,也就是差分约束.通过差分约束变换出一个图,再使用Dijikstra算法的链表优化形式而不是vector形式(否则超时). #include< ...

  10. scrapy框架爬取图片并将图片保存到本地

    如果基于scrapy进行图片数据的爬取 在爬虫文件中只需要解析提取出图片地址,然后将地址提交给管道 配置文件中:IMAGES_STORE = './imgsLib' 在管道文件中进行管道类的制定: f ...