多线程编程初探——OO第二单元作业回顾
一、作业设计策略
1)执行FAFS策略的单部电梯
由于对多线程不是很了解,于是采用了理论课上介绍的生产者消费者模型作为设计模板(也是很多同学一开始的做法):将请求队列作为共享对象(托盘),名为Input_handler的类处理输入的请求并将请求加入到请求队列(相当于生产者),调度器类则负责从请求队列中取请求并直接执行(相当于消费者)。
同步控制方面,由于共享对象queue中的请求队列是可变对象,因此可能有线程不安全的问题,简单的解决方法是将能够修改该可变对象的两个方法add/pop设置同步控制synchronized即可,这样保证两个线程不会同时访问修改共享对象。
在本次作业中我选择了好写的傻瓜调度,线程间的协同也仅仅是调度器和输入处理各自和共享对象交互,总体设计难度不大。
2)执行捎带策略的单部电梯
此次作业需要考虑电梯的性能,也就意味着需要调整原有的设计架构,加入捎带功能。我考虑过两种捎带策略,区别在于“电梯知道多少信息”:在第一种架构中,电梯只需要知道接下来向哪个方向运动,当前层是否开门,不需要知道电梯上的乘客信息;而在第二种架构中,电梯知道乘客的信息,可以自主决定向哪运动,向哪开门。
明显可以看出,第二种架构相当于赋予了电梯一定的自主决定权,而第一种情况下的电梯则只具备执行请求功能。第一种设计的电梯内聚性更低(代价是电梯控制器的复杂度明显上升了),但耦合度较高,每次运动、开门、上下乘客时都要交互。第二种设计的电梯内聚度比较高,但是耦合度低,电梯只需要从请求队列中取请求进行执行,每运动一层自行决定是否上下乘客。
其实总的任务是一样的,不同的设计只是在如何分解大的任务方面存在分歧,上面的第一种设计相当于在第二种设计上把“请求”更细致地解析为了每一层的移动策略,在实际运用中更安全同时在某些情形下可以实现更为精细的控制,但第二种设计实现起来无疑更加简单可靠。
在沃艾思(操作系统)课程压力较大的情况下我最后还是求稳选择了第二种架构:由于只有单部电梯不涉及请求分配,砍掉了调度器只保留请求队列,在请求队列中编写两个请求分配方法(主请求和捎带请求);电梯在乘客队列为空时,向请求队列申请主请求(没有主请求则wait),拿到主请求开始工作,每运行一层后从队列中获得一个捎带请求,如果捎带请求不为空(有人可以捎带)或者有乘客要下电梯则开门,开门后先下乘客(到达目的地的乘客)后上捎带乘客(while 捎带请求不为null则get捎带请求);捎带请求并入乘客队列(副请求),在执行完主请求时选择一个最近的副请求升级为主请求;所有请求执行完毕且输入关闭时电梯下班(退出线程)。
关于捎带策略:官方推荐仅在能上且目的地和电梯运行方向一致时才捎带,但实际上有一种卑鄙的外乡人换乘策略更简单,且效果也不差(由于这部电梯可以无限载重),即在能上时就把人带上,每次执行完一个请求后都切换到一个目的地距离当前楼层最近的副请求,这样不仅可以在电梯运行时间上性能优于ALS捎带策略,同时还节省了开门的时间。
线程协作关系基本上还是生产者-消费者模式,托盘增加了一个获得捎带请求的方法(花式消费)。
线程同步控制方面,将增加请求、获取主请求、获取捎带请求这三个方法设为synchronized即可。
3)多电梯协同工作
这次作业主要的变化是电梯数量和电梯停靠楼层的变化(载重问题只需要在上次作业的基础上根据电梯当前人数限制捎带即可),前者使得工作的线程数增多了,后者则使得单个乘客请求的执行可能需要电梯间合作执行。具体到编程实现,需要有策略地拆分某些请求,并保证拆分掉的请求按一定的次序得到执行(先要执行完前一个子请求才能执行后一个)。此外,对于某些不需要拆分的请求,由于三部电梯运行速度不一致,调度策略也不是唯一的。
首先是拆分请求,所谓拆分实际上就是把单部电梯无力完成的任务拆分为两部电梯协作完成的任务,第一部电梯接到乘客将其送到换乘楼层(两部电梯的公共楼层),第二部电梯从换乘楼层接到乘客并把乘客送到目的地。不难知道,最简单的拆分策略是把换乘楼层设置为三部电梯的公共楼层1和15,这样所有请求都能被拆分后由单部电梯执行;更优的策略则是考虑选择能使用户乘坐电梯时间最小化的换乘楼层;当然,可以在短时间内做一些优化,例如考虑合作者当前所在的楼层和合作者搭载的乘客(合作者有自己的运行计划,且前往换乘楼层需要时间),但是这无疑使问题变得相当复杂,且只对极少情况下的性能有提升(考虑到三部电梯神奇的楼层分布使得请求拆分的情况较少),所以我仅仅考虑了用户乘坐电梯的总时间。
其次是保证请求的执行顺序,这里我类比了一下work-thread模式:我的请求队列相当于channel,电梯相当于worker,不同的是这里的worker执行的工作可能是多工序的,worker完成自己可以完成的那道工序之后会通知channel “这一步我搞定了”。而具体到我的实现,就是把拆分后的请求(两道工序)打包起来送给一部电梯,电梯完成第一个子请求(第一道工序)后将第二个子请求加入请求队列(通知channel第一道工序已经完成)。这样便能保证请求按顺序执行。至于两个子请求如何打包传入,我新建了一个request类用来存这两个子请求(第二个子请求可以为空,表示该请求没有被拆分)。
那么剩下的问题就是:电梯如何得到请求?既可以是电梯向调度器“抢”请求,也可以是调度器根据电梯的状态将请求分配给电梯。同样地,因为我不希望上调调度器的复杂度,所以我还是继承了上次作业的“电梯抢请求”的方式,调度器只需要负责分配和检查有无捎带乘客即可。
由于整体架构和上次作业相同,线程协作和同步控制的方式总的来说和之前一样,区别在于我修改了请求队列(调度器)的add方法,在每次加入请求时判断是否需要拆分请求(如果无法由单部电梯执行则需要拆分)。
二、基于度量分析程序结构
1)作业程序结构分析
a.hw5
可是看他的架构平平无奇
方法复杂度:
类复杂度:
代码总规模:
类图:
线程协作关系:
b.hw6
方法复杂度:
可见分配捎带请求的方法相对复杂。
类复杂度:
从此次作业开始电梯类变得复杂。
代码总规模:
类图:
可以看出整体的架构和第五次作业一致,类的内部属性方法进行了扩展。
线程协作关系:
c.hw7
类复杂度:
显然,由于需要自行判断运行方向和开关门策略,电梯类的复杂度较高。
方法复杂度:
可见请求队列(调度器)中拆分请求和分配主请求的方法复杂度较高。
总代码规模:
类图:
整体结构依然和前两次作业一致,内部的属性方法进一步复杂化,引入了Request和Type两个类,前者用于容纳一个请求拆分得到的两个子请求,后者用于规范化电梯的属性。
线程协作关系:
2)优缺点评论
三次作业采用的架构基本一致,优点在于沿袭了设计模式,线程安全得以保证;缺点在于把相当一部分运行策略封装进了电梯,调度器的作用仅剩下“提供请求让电梯来拿”,无法根据电梯当前的楼层和电梯的运行速度做针对性的分配策略,因而性能并不顶尖。
3)SOLID原则
a.SRP单一责任原则
较好实现——电梯只负责运行和在停靠楼层与调度器交互(确定是否开门),调度器负责拆分和分配请求,内置请求队列;输入处理负责向调度器的请求队列中加入新的请求。
如果不增加新的类,则所有的工作将由这几个类分担,想要减少一个类的责任,势必增加另一个类的,如不新增类(例如电梯控制器、楼层等等)很难更好地符合单一责任原则。
b.OCP开放封闭原则
实现较好,三次作业架构始终不变,第六次作业扩展了捎带请求方法,第七次作业扩展了拆分请求的方法。
c.LSP里氏替换原则
不适用,并未使用继承,第三次作业用可变参数的方法实现了多电梯。
d.ISP接口分离原则
不适用,程序除了线程使用了Thread接口,没有使用其他接口。
e.DIP依赖倒置原则
虽然我没有使用抽象接口,但是的确对程序设计进行了抽象而非针对实现进行编程,可以说实现地较好。
三、程序bug
1)公测中出现的bug
本次公测(至今最灰暗的一次)中我的程序出现了两个逻辑上的错误,并且是小规模手造数据难以暴露的bug,加上我没有自己在课下进行自动化测试,中测样例又比较仁慈,因此强测分数十分喜人(如果分数评论标准和田径比赛一样的话)。
第一个错误是电梯arrive楼层之后和调度器的交互,应当先确定当前楼层能够停靠之后再进行捎带和下乘客的交互,但是我将这两者(交互和确定停靠)的顺序写反了,导致会出现如下情况:电梯拿到一个捎带请求,发现停靠不了,然后请求就没有加入电梯,没有加入电梯……这导致在某些情况下乘客和消失了一样(应该是掉入电梯井了?),程序无法正确执行。
第二个错误是电梯是在乘客上电梯之后才增加载重,而不是在相应请求之后就增加载重,于是一开始的主请求在被响应之后,电梯载重为0,然后在去接主请求的路上已经装满了捎带请求,这样等到电梯终于到了主请求上电梯的楼层时,主请求如果直接进入,那么违反了载重限制;如果不上电梯,那么电梯的主请求没有得到执行。两种情况都会出错。
这两个bug
一个发生elevator
类中负责电梯交互的方法interact
里,另一个是在共享对象请求队列的mainreq
方法中。都与线程安全无关,是电梯运行逻辑上的错误。
2)bug与设计结构的相关性
本次作业会出现这两个bug,和我的设计结构是有一定关系的。
第一个bug的出现是因为在继承上次设计的基础上对于“判断当前楼层能否停靠”判断的位置不恰当,放在了拿到请求后(本应该在拿到请求前判断),导致可能出现请求未被执行的情况。
第二个bug——由于我采用的是“主请求+捎带”的响应请求逻辑,所以和现实生活中电梯的逻辑是有一定差异的:响应了主请求,电梯事实上就做出了保证一定要接到主请求,此时即应留出主请求的空位确保其一定能搭乘电梯。但是我在考虑人数限制的时候,依然是按照现实生活中的电梯运行逻辑来控制的:上人载重+1,下人载重-1,看似没问题,实际可能出现主请求进入满载电梯的情况。
### 四、互测策略
本单元前两次作业放了几个空刀,没有发现同屋同学的bug,第三次作业没有进入互测,所以总的来说互测经历没有太多东西值得总结。
五、学习心得
1)如何保证线程安全
在保证线程安全方面,我认为应该重点关注共享对象的访问控制,同时对可能死锁的情况予以提前考虑。当然在三次作业中,synchronized方法锁基本上就能解决所有问题,且性能损失可以接受,不太需要用到原子操作、对象锁等等;同时作业中只有一个共享资源、不太可能出现死锁,选取合适的设计模式加以改造,可以比较好地处理作业中的线程安全问题。
2)设计原则
设计原则方面,我认为应当参考SOLID原则进行程序设计,这样可以保证程序的可靠性且易于扩展。同时我们应当善用不同的设计模式,提高程序效率、保证程序安全性。
多线程编程初探——OO第二单元作业回顾的更多相关文章
- 【OO学习】OO第二单元作业总结
OO第二单元作业总结 在第二单元作业中,我们通过多线程的手段实现了电梯调度,前两次作业是单电梯调度,第三次作业是多电梯调度.这个单元中的性能分要求是完成所有请求的时间最短,因此在简单实现电梯调度的基础 ...
- 电梯也能无为而治——oo第二单元作业总结
oo第二单元作业总结 一.设计策略与质量分析 第一次作业 设计策略 在第一次作业之前,我首先确定了生产者--消费者模式的大体架构,即由输入线程(可与主线程合并)充当生产者,电梯线程充当消费者,二者不直 ...
- OO第二单元作业总结【自我反思与审视】
第二单元作业的完成史,就是一部心酸的血泪史…… 多线程的出现为我(们)打开一片广阔的天地,我也在这方天地摸爬滚打,不断成长!如果说第一单元之前还对Java语法有所了解的话,那么这单元学习多线程则完全是 ...
- oo第二单元作业总结
oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...
- 北航OO第二单元作业总结(2.1~2.3)
在经过第一单元初步认识面向对象编程思想后,本蒟蒻开始了第二单元--多线程部分的学习.本单元的作业是构造符合条件的"目的选层电梯"模型,自行设计调度算法,进行合理调度,完成所有乘客的 ...
- OO第二单元作业小结
前言 转眼已是第九周,第二单元的电梯系列作业已经结束,终于体验了一番多线程电梯之旅. 第一次作业是单电梯的傻瓜调度,虽然是第一次写多线程,但在课程PPT的指引下,写起来还是非常容易:第二次作业是单电梯 ...
- 电梯模拟系统——BUAA OO第二单元作业总结
需求分析 官方需求 本次作业需要模拟一个多线程实时多电梯系统,从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出. 本次作业电梯系统具有的功能为:上下行, ...
- 你电梯没了—OO第二单元作业思考
写在前面 这三次电梯调度作业,主要是学习多线程并行操作,对于各个线程的时间轴的把握,互相的配合与影响,通过使用锁来解决访问冲突等方面. 个人在学会Thread相关操作之外,写出来一些奇怪结构的诡异操作 ...
- OO第二单元作业分析
前言 这一单元关于线程安全的作业结束了,在助教提供的接口的帮助以及老师提供的设计模型的指导下,这三次作业还是相对轻松地完成了,中间也没有出现什么bug,可能就是因为简单的逻辑不容易出错吧,可惜两次都由 ...
随机推荐
- 爬虫必备—BeautifulSoup
BeautifulSoup是一个模块,该模块用于接收一个HTML或XML字符串,然后将其进行格式化,之后便可以使用他提供的方法进行快速查找指定元素,从而使得在HTML或XML中查找指定元素变得简单. ...
- H5禁止手机自带键盘弹出
一个功能中用到这个, 调用软键盘, 不想弹出手机默认的输入法 网上找了个 http://blog.csdn.net/qq_24147051/article/details/52958610 处理方式 ...
- 关于modelmap.addAttribute("",)转到jsp页面获取不到值的问题
问题一,可能是你设置的web.xml的头有问题 掉坑里好一会,发现我默认生成的web.xml中头部的配置是 <!DOCTYPE web-app PUBLIC "-//Sun Micro ...
- (WCF) 多线程 (Multi-threading) 和 并发性 (Concurency)
问题:WCF 有个Server端,还有个Client端,他们之间是如何进行并发,多线程通信的呢?多个Client端同时访问Server,如何保证Server端的操作线程安全呢? 在理解WCF Conc ...
- ShareSDK分享和SMS的使用
使用的是第三方的Mob来实现分享和发送短信的Demo 如图是项目的结构,结合了分享,发送短信,读取短信内容的功能 代码地址:https://github.com/1825492258/MobShare ...
- 实战:Nginx如何让用户通过用户名和密码认证访问WEB站点
有时我们会有这么一种需求,就是你的网站并不想提供一个公共的访问或者某些页面不希望公开,我们希望的是某些特定的客户端可以访问.那么我们可以在访问时要求进行身份认证,就如给你自己的家门加一把锁,以拒绝那些 ...
- Asp.Net MVC4 系列-- 进阶篇之路由
原文 http://blog.csdn.net/lan_liang/article/details/22993839 创建一个路由 打开 RouteConfig.cs ,发现已经创建了一个默认路由 ...
- iOS设计模式 - 装饰
iOS设计模式 - 装饰 原理图 说明 1. cocoa框架本身实现了装饰模式(category的方式实现了装饰模式) 2. 装饰模式指的是动态的给一个对象添加一些额外的职责,相对于继承子类来说,装饰 ...
- [翻译] WZFlashButton
WZFlashButton This is a custom button with flash-like effect. Inspired by Ripple Effect. 这是一个自定义的按钮, ...
- Connection to linux server with ORACLE SQL DEVELOPER
1.Link name is random 2.username and password is database account 3.host name is ip address ifconf ...