BUAA_OO_博客作业2——多线程电梯之旅
0 写在前面
终于,我们结束了OO第二个周期的作业,这几次作业也就是我们所早有耳闻的“电梯”作业。
现在就来写关于这几次作业的总结,主要会分析自己的设计方案和总体收获。
1 多线程协同与同步控制(设计方案)
我的这三次作业都采用了多线程的方式来编码。主要是因为作为一名重修生(去年一些意外,放弃了OO),我早就知道电梯最后会演变成多电梯等。
其次就是,我也知道这次作业是为了锻炼我们的多线程编码能力、了解线程同步与安全设置的,所以我也从一开始就采用了多线程的设计。
我的程序主要分为几大部分,并且这几个部分也就是我的几个线程:
- 输入处理部分:使用给定的接口,处理输入的数据并将处理后的请求放入请求缓冲池中。
- 调度器部分:从请求缓冲池读取待处理请求,根据情况分给不同的电梯或者做其他处理后分给电梯(最后一次用到)。
- 电梯部分:每个电梯有自己的待处理请求池,电梯根据自己请求池内的人和自己的运行逻辑进行运行。
以上三个部分在前两次作业中为三线程,第三次作业中有三个电梯线程和前两个线程,共计五个线程。
1.1 多线程协同
这部分主要讲三个部分如何互相协助,完成任务。
我们称输入处理部分为InputHandler,调度器部分为Scheduler,电梯部分为Elevator。以前两次作业为例(第三次仅仅是增加两部电梯,和前两次没有本质区别)进行说明。
1.1.1 InputHandler
我的InputHanler参考了给出的demo,使用while-true循环的方式进行读输入,当读入为null(即没有后续请求)时break退出while循环,并且修改标志位通知其他线程后续将没有请求输入了。
InputHandler通过将读入的请求存入与调度器共用的缓冲池,实现告知调度器请求的目的。每当InputHandler读入一个请求时,将调用“缓冲池.notify”,试图将调度器唤醒,通知调度器有新的请求。
1.1.2 Scheduler
我的Scheduler也使用while-true循环,但是每次分配任务完成后会根据结束标志位判断自己应当结束或者wait(等待后续请求)。因此Scheduler将不会因为盲目while-true占用过多的CPU时间。
当有新的请求进入时,Scheduler将会因为"缓冲池.notify"而醒来,进行任务的分配。
电梯通过“注册”的方式进入Scheduler,即在主线程中new出来的电梯使用Scheduler的方法进行注册,Scheduler内部有一个注册列表,内有所有电梯。
每当Scheduler将某一任务分配给特定的电梯x时,将会调用“x.notify”以只叫醒x电梯。这样也不会叫醒无关电梯而导致CPU时间浪费。
当Scheduler分配所有任务完成时,如果发现InputHandler已经将结束标志位置为true,则自己也会退出while-true循环,并且将自己与电梯沟通的结束标志位置为true,然后遍历注册列表的所有电梯进行唤醒。
通过这样的方式,可以保证所有电梯都能正常接收到Scheduler发出的结束信号,避免了有的电梯无限wait无人叫醒的情况发生。
1.1.3 Elevator
我的Elevator也使用while-true循环,但是每当电梯内没有待执行任务时将会根据Scheduler发出的结束标志进行wait或者退出。因此Elevator也不会因为盲目while-true而占用过多CPU时间。
电梯运行算法主要参考了LOOK算法,LOOK算法是磁盘调度算法的一种,这里我根据磁盘调度的LOOK我模仿了一个电梯调度的LOOK算法;
我采用LOOK算法的原因有以下几条:
- 时间相对较短,即性能更好
- 比较符合我们日常生活中电梯的行为
- 不会出现饥饿显现(即某请求因为调度算法的原因产生长时间等待)
LOOK算法的具体实现就不在这里给出了,总之LOOK是和ALS稍有不同的一种算法,我个人认为LOOK算法在大量请求的情况下和ALS相比有较大的优势,因此采用了LOOK算法。
电梯将会在自己没有待处理任务时进入wait状态,并且会在自己收到任务时被叫醒。
电梯将会在自己没有待处理任务并且收到Scheduler的结束信号时结束,避免无限wait造成程序无法结束。
1.1.4 其他想说的
我的电梯在设计之初就采用了LOOK算法,这不仅仅是因为我早就猜到(知道)后续会需要捎带请求,更是因为我深知傻瓜电梯实际上是没有意义的,真正的电梯必须有自己的效率。
并且如果我第一次就写好LOOK算法的话后面就可以摸鱼了!
因此我的第五次作业就已经是LOOK算法的电梯了。拜此所赐,我第一周de了不少bug,但是后两周(尤其是第六次作业的时候)我确实轻松了不少。别的不少同学都需要重新思考自己的数据组织,而我除了加个ARRIVE几乎什么都不用干!
1.2 多线程同步(控制)
多线程程序面临的最大的问题之一就是线程同步。所谓线程同步就是所有线程对于共同的访问资源,需要同步信息。在多线程程序中,经常出现read-and-write等操作,因此需要注意共享资源的访问控制。
Java提供了synchronized关键字以实现简单的线程同步。我的程序中使用并且也只使用了synchronized关键字来实现这部分的需求。
在我的程序中,有两部分线程共享资源,一个是初始请求池,另一个是电梯待处理请求池。初始请求池可以由Scheduler和InputHandler操作,分别有读和写需求。
因此我在他们访问这一资源时首先使用synchronized关键字“锁住”这一资源,再对资源进行读写操作。
在电梯的待处理请求池的处理上,我也采用了类似的方式,即封装好的方法内部有synchronized关键字锁住的操作对象,以此保证线程安全。
1.3 自己的思考
在线程同步的学习中,关于需要考虑线程安全的地方,我自己总结了一下几个关键点:
- 不同线程访问(修改)同一共享资源
- 对于这次访问有迫切的绝对需要获得正确状态的需要
对于第一点,我们都可以很好地理解。但是对于第二点,我有自己的一些想法:
假设我们有两个人使用同一账户存取款,我们不妨设为A和B;A的操作是向账户里存入100元,B的操作只是读取账户内现有金额,并且在金额大于500时去吃饭。
根据上述设定,A线程的操作是不停向账户内存入100元,每存入一次休息一会;B线程的操作是不停访问账户,当金额大于500时去吃饭。
假设B对于去吃饭并没有迫切的需要,也就是说B并不会因为某次该读501时读到401而饿死,那么我认为B在访问账户内容的时候其实是不必上锁的。
也就是说,假设我们的线程对于某个共享资源的状态没有迫切的需要(即不是特别在意实时反应的那种),只是“日常访问”的话,那么其实对于读操作来说没有必要必须锁住共享对象。
2 基于度量分析自己程序的结构
在这部分中,我们将第5、6、7次作业分别讨论。(长图警告!(也可能是长列表))
注:
- ev(G) Essentail Complexity
- iv(G) Design Complexity
- v(G) 圈复杂度
2.1 第五次作业——傻瓜电梯(我的是LOOK电梯)
2.1.1 复杂度分析
总体来看在几个比较大的方法的复杂度还是偏高的,值得反思,是否可以拆分为更多的方法?
2.1.2 类图
类图中可以看出,主要有三大线程,和Main线程。Main负责完成启动其余线程的工作。
StopFlag和Person是为了方便,自己封装的两个类。(StopFlag就是结束信号,Person是电梯内的人)
2.1.3 SOLID原则
SRP单一责任原则:实现较好
OCP开放封闭原则:实现较好
LSP历史替换原则:由于没有继承,不考虑
ISP接口分离原则:由于没有使用除Runnalbe以外的接口,不考虑
DIP依赖倒置原则:实现较好
2.2 第六次作业——ALS电梯(我的还是LOOK电梯)
2.2.1 复杂度分析
2.2.2 类图
2.2.3 SOLID原则
SRP单一责任原则:实现较好
OCP开放封闭原则:实现较好
LSP历史替换原则:由于没有继承,不考虑
ISP接口分离原则:由于没有使用除Runnalbe以外的接口,不考虑
DIP依赖倒置原则:实现较好
2.3 第七次作业——蛇皮电梯x3(我的是LOOK电梯x3)
2.3.1 复杂度分析
2.3.2 类图
2.3.3 SOLID原则
SRP单一责任原则:实现较好
OCP开放封闭原则:实现较好
LSP历史替换原则:由于没有继承,不考虑
ISP接口分离原则:由于没有使用除Runnalbe以外的接口,不考虑
DIP依赖倒置原则:实现较好
2.4 总结
实际上,我的三次作业的类就没有变过。所以三次的类图和SOLID分析是同样的。
主要是内部的方法实现发生了改变。不过实际上应该通过继承的方法来实现新的设计。
3 分析自己程序的bug
实际上,我的程序bug基本都在自测阶段处理掉了。
不过,在强测和互测阶段出现过一个超时bug,原因是因为我的电梯在转向的过程中处理状态发生了问题(第二次电梯作业我做了少许修改,结果导致了这个bug)。
不过还是有很多已经被我私下处理掉的bug需要注意的,主要有一下几种:
- 过早停止的电梯拒载,解决方案:设置StopFlag和合适的算法,使电梯在执行完所有任务时才会停止;
- 电梯无限wait无法停止,解决方案:在Scheduler结束时叫醒所有电梯,防止电梯wait但是Scheduler停止,无人叫醒电梯导致bug;
- 早期实现中,我使用暴力轮训导致CPU超时,解决方案:使用notify-wait方式防止无用线程无限被调用,减少CPU时间
4 分析自己发现别人bug采用的策略
实际上,我这几次作业并没有认真hack别人的电梯。一方面由于自己有很多其他事情要干,活跃度就交给roommate吧!另一方面是因为我真的不想hack了。
实际上我和别的同学交流过程中发现,不少bug都是由于线程安全导致的。对于此类bug,我们需要着重观察对方程序对于共享对象访问的处理。
另外还有一种bug就属于真正的程序逻辑bug,此类bug大多因为程序员设计漏洞导致。对于这种bug,我们需要真正理解对方想要实现的算法,并且把自己的思考和对方的实现进行对比来发现。
5 心得体会
经历了电梯系列作业的洗礼,我们都有不少收获。
先说关于多线程编程调试的问题,多线程程序难就难在难以调试和复现bug。在这样的情况下更需要我们使用清晰的程序逻辑,方便自己通过“走查”的方式分析自己的bug。
多线程程序另一个重要的问题就是线程安全问题。我认为我们在做设计的时候有两种思路:
- 使用自己封装(别人封装)的线程安全类
- 在程序中注意线程安全,使用synchronized关键字等
我认为第一种方式是一种比较稳妥的方式,虽然我实际上是使用的第二种方法。事后我也考虑的自己的设计进行了反思。第一种确实是优于第二种的。
因为第一种线程安全类在使用的时候不是需要时刻注意的,并且是一种一劳永逸的方式。但是第二种在你编写新的部分的时候,必须也考虑其他线程是否会使用。
在设计原则上,我们应该坚持SOLID原则,因为这样的程序是易于维护、扩展的。另外我们应当善用单例模式、观察者模式、工厂模式等。以上几种模式都是为了更好地扩展维护的。
以上
转载于:https://www.cnblogs.com/heyedan/p/10742265.html
BUAA_OO_博客作业2——多线程电梯之旅相关推荐
- BUAA_OO_博客作业3——规格
BUAA_OO_博客作业3--规格 • 梳理JML语言的理论基础.应用工具链情况 JML是java modeling language的缩写,是一种描述性质的语言.有一定的语法规则. 这种语言被用来描 ...
- 【BUAAOO】第四次博客作业
[BUAAOO]第四次博客作业 说点闲话 繁忙了一个学期的面向对象课程终于快要正式结束了,虽然忙,却苦乐参半.许多"第一次"献给了OO--第一次熬夜,第一次高强度代码训练,第一次独 ...
- 【面向对象设计与构造】第一次博客作业
[面向对象设计与构造]第一次博客作业 一.程序结构分析 1. 第一次作业 类图 由于第一次作业难度较低,实现起来也不需要很复杂的算法,因此在编写程序的时候只建立了两个类,Main类主要负责多项式的读入 ...
- OO第三次博客作业——规格
OO第三次博客作业--规格 一.调研结果: 规格的历史: 引自博文链接:http://blog.sina.com.cn/s/blog_473d5bba010001x9.html 传统科学的特点是发现世 ...
- 作业二:个人博客作业内容:需求分析
作业二:个人博客作业内容:需求分析 怎样与用户有效沟通获取用户的真实需求? 访谈,正式访谈系统分析员将提出一些事先准备好的具体问题:非正式访谈中,分析人员将提出一些用户可以自由回答的开放性问题,一鼓励 ...
- C语言第二次博客作业---分支结构
C语言第二次博客作业---分支结构 一,PTA实验作业 题目1.计算分段函数 本题目要求计算下列分段函数f(x)的值 1.代码 double x, result;scanf("%lf&quo ...
- OO--第三单元规格化设计 博客作业
OO--第三单元规格化设计 博客作业 前言 第三单元,我们以JML为基础,先后完成了 PathContainer -> Graph -> RailwaySystem 这是一个递进的过程,代 ...
- DS博客作业08--课程总结
1.当初你是如何做出选择计算机专业的决定的? 当初选择计算机专业是因为比较喜欢电脑,有事没事喜欢慢慢摸索一些关于电脑的乱七八糟的东西.因为接触电脑比较早,所以对电脑的操控什么的都比较熟练.当然我也特别 ...
- 2018091-2博客作业
此作业的要求参见https://edu.cnblogs.com/campus/nenu/2018fall/homework/2101 1. 建博客(1分) 在 cnblogs.com (博客园)建账号 ...
最新文章
- mysql切换到使用openssl_OpenSSL可以用来调试到MySQL服务器的SSL连接吗?
- 基于tcp协议的socket
- Apache服务器错误问题Internal Server Error
- 使您的Spring Security @Secured注释更干燥
- mysql typeindex_explain mysql的type字段,索引的类型
- java中string的方法_java中String的常用方法
- SpringBoot整合Redis 主从复制_01
- Python序列基本操作(三)
- matlab 绘花,【原创】使用matlab绘制菊花和玫瑰花
- Mybatis简单入门及配置文件标签详情
- oracle plsql创建表空间,Oracle在PLSQL Developer上创建表空间和用户脚本 - 龙卷风的日志 - 网易博客...
- Ubuntu进入pycharm创建的虚拟环境的方法(以及如果你安装了anaconda等其它修改了环境变量的东西该怎么进)
- Linux Performance Observability Tools
- Comparable 与 Comparator 比较
- 群论基础速成(6):五大著名群族
- c语言n层文字塔程序的结构图,精馏塔中由塔顶向下的第n-1,n,n+1层塔板,其气相组成关系为( )...
- Bootstrap(10) 进度条媒体对象和 Well 组件
- C语言编写一个函数,实现计算并返回一个整数的平方(或立方)
- Icon是什么,在线实现图片转Icon的方法
- 怎样在oracle中加下划线,Oracle使用like查询时对下划线的处理方法
热门文章
- 树莓派 3B 入手 基础篇(一)
- 文本框/文本域,部分文字不可编辑(输入/删除等)。
- outlook中实现邮件自动回复
- 共享文件服务器实施方案,企业文件共享服务器搭建思路
- 1.《如何构建敏捷项目管理团队》之成为好教练(摘要)
- “你的电脑将在一分钟后自动重启”、卡在“欢迎”界面、网络/声音图标显示红叉、无法连接到服 务、网络图标消失、开机需要同时使用用户名和密码登录等的解决方法
- python(数据分析)第3天:坐标轴显示中文,旋转
- 宜人贷蜂巢API网关技术解密之Netty使用实践 1
- sql serve2008的增删改查操作
- 王者荣耀s12赛季服务器维护,王者荣耀S12赛季更新内容 排位不掉星方法