OO_Unit2总结
OO_Unit2总结
(1) 多线程协同控制设计策略
总体信号通讯策略
本单元作业,我采用的是生产者-消费者模式加类观察者模式。
通过分析指导书给出的需求,我将最终我要实现的程序简化为了“输入-调度器-电梯”,输入线程向调度器里输入请求,调度器保存请求并根据电梯的状态响应电梯的索要请求,电梯运行时,在合适的时候向调度器索要请求。
而类观察者模式,则是指电梯和乘客之间的互动方式,电梯在合适的时候向乘客发出信号,乘客收到信号之后,自行选择是否离开电梯。之所以说是类观察者模式而非观察者模式,是因为在设计时的理念是类似于观察者模式的,但实现时没有严格遵循观察者模式的方式,而是以一种耦合度较高的形式出现在了我的代码里:电梯直接遍历乘客的ArrayList,通过判断其是否符合条件选择是否使乘客离开。
输入线程与调度器的通讯策略
两者之间通过ArrayList完成,对读写操作加锁保证线程安全。
电梯线程与调度器之间的通讯策略
ElevatorThread持有Scheduler和对应的Elevator(存放电梯运行时的信息),Scheduler持有对应的ELevator,电梯线程和调度器之间的通讯包括电梯线程直接调用调度器方法的直接通讯和通过Elevator的间接通讯。好处是几乎在任何时候都可以调用相应的方法达到通讯的目的,并且相应的逻辑关系非常符合直觉,坏处是耦合十分严重,电梯线程和调度器和电梯之间出现了循环依赖,锁的粒度过粗,因为要应对三者的操作。
(2)第三次作业架构设计可拓展性
| 可拓展性 | 还行 | 第一次->第二次->第三次,我都没有对第一次的架构做出巨大的修改,基本都是非常顺利地拓展第一次的架构。功能设计上,总调度-分调度-电梯的三级结构使得我可以顺利将任务分段,但是这样开弓没有回头箭,损失了一部分的性能。而这样的架构也使我可以在新增需求时快速分解需求,分配给三级结构中的每一级,从而实现拓展。 |
|---|---|---|
| S | 尚可 | 输入线程负责输入,电梯线程负责电梯运行,电梯类保存电梯状态,调度器负责调度请求。调度器分为总调度器和分调度器,总调度器负责给各个分调度器分配请求,分调度器负责给电梯调度请求。职责明确。但是输入线程额外地承担了启动电梯线程的任务,电梯类额外地承担了通知乘客离开的任务,这点不太好。 |
| O | 不好 | 从第二次到第三次,增加新的需求带来的就是我对已有类的修改,没有做到开闭原则。 |
| L | 好 | 没有子类怎么会不满足LSP呢(((( |
| I | 好 | 没有接口 |
| D | 坏 | 依赖实例而不是依赖抽象,在我的ElevatorThread、Scheduler、Elevator里轮番上演 |
(3)基于度量分析程序结构
第一次作业
| oo_u2hw1 | (default package) | Elevator | getPassengers | 3 | 1 | 0 |
|---|---|---|---|---|---|---|
| oo_u2hw1 | (default package) | Elevator | isEmpty | 3 | 1 | 0 |
| oo_u2hw1 | (default package) | Elevator | getState | 3 | 1 | 0 |
| oo_u2hw1 | (default package) | Elevator | setState | 3 | 1 | 1 |
| oo_u2hw1 | (default package) | Elevator | getFloor | 3 | 1 | 0 |
| oo_u2hw1 | (default package) | Elevator | setFloor | 3 | 1 | 1 |
| oo_u2hw1 | (default package) | Elevator | fresh | 36 | 11 | 1 |
| oo_u2hw1 | (default package) | Elevator | move | 8 | 3 | 0 |
| oo_u2hw1 | (default package) | Elevator | notifyAllPassengers | 10 | 3 | 0 |
| oo_u2hw1 | (default package) | ElevatorThread | ElevatorThread | 4 | 1 | 2 |
| oo_u2hw1 | (default package) | ElevatorThread | run | 37 | 5 | 0 |
| oo_u2hw1 | (default package) | InputThread | InputThread | 3 | 1 | 1 |
| oo_u2hw1 | (default package) | InputThread | run | 19 | 3 | 0 |
| oo_u2hw1 | (default package) | MainClass | main | 9 | 1 | 1 |
| oo_u2hw1 | (default package) | Scheduler | Scheduler | 3 | 1 | 1 |
| oo_u2hw1 | (default package) | Scheduler | put | 4 | 1 | 1 |
| oo_u2hw1 | (default package) | Scheduler | getMainRequest | 35 | 8 | 0 |
| oo_u2hw1 | (default package) | Scheduler | judgeOpen | 19 | 5 | 0 |
| oo_u2hw1 | (default package) | Scheduler | isEnd | 4 | 1 | 0 |
| oo_u2hw1 | (default package) | Scheduler | setEnd | 4 | 1 | 0 |
| oo_u2hw1 | (default package) | Scheduler | isEmpty | 3 | 1 | 0 |
| oo_u2hw1 | (default package) | Scheduler | notifyAllPassengers | 12 | 3 | 0 |



从类图中可以看到,MainClass仅起到启动各个线程的作用,之后所有事情都靠其他线程完成。InputThread管理输入并将Request放入Scheduler中,ElevatorThread负责模拟电梯的运行,电梯运行过程中会改变电梯状态,通知电梯中人离开和调度器中人进来。总体来说结构清晰,各个类职责明确。美中不足是fresh方法和getMainRequest方法有些复杂,其中一个是改变电梯状态,另一个是决定当前的主请求,较为复杂是意料之中的。
第二次作业
| Project Name | Package Name | Type Name | MethodName | LOC | CC | PC |
|---|---|---|---|---|---|---|
| oo_u2hw2 | (default package) | Elevator | Elevator | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | Elevator | getPassengers | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Elevator | isEmpty | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Elevator | isFull | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Elevator | getState | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Elevator | setState | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | Elevator | getFloor | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Elevator | getRealFloor | 6 | 2 | 0 |
| oo_u2hw2 | (default package) | Elevator | setFloor | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | Elevator | getName | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Elevator | fresh | 36 | 11 | 1 |
| oo_u2hw2 | (default package) | Elevator | move | 8 | 3 | 0 |
| oo_u2hw2 | (default package) | Elevator | notifyAllPassengers | 13 | 3 | 0 |
| oo_u2hw2 | (default package) | ElevatorThread | ElevatorThread | 5 | 1 | 3 |
| oo_u2hw2 | (default package) | ElevatorThread | run | 46 | 8 | 0 |
| oo_u2hw2 | (default package) | InputThread | InputThread | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | InputThread | run | 28 | 4 | 0 |
| oo_u2hw2 | (default package) | MainClass | main | 6 | 1 | 1 |
| oo_u2hw2 | (default package) | ScheduleAll | addScheduler | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | ScheduleAll | addElevator | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | ScheduleAll | put | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | ScheduleAll | setEnd | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | ScheduleAll | schedule | 32 | 8 | 0 |
| oo_u2hw2 | (default package) | ScheduleAll | askFor | 18 | 5 | 1 |
| oo_u2hw2 | (default package) | ScheduleAll | startAll | 5 | 2 | 0 |
| oo_u2hw2 | (default package) | Scheduler | Scheduler | 3 | 1 | 1 |
| oo_u2hw2 | (default package) | Scheduler | put | 4 | 1 | 1 |
| oo_u2hw2 | (default package) | Scheduler | getRequests | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Scheduler | getMainRequest | 35 | 8 | 0 |
| oo_u2hw2 | (default package) | Scheduler | judgeOpen | 19 | 5 | 0 |
| oo_u2hw2 | (default package) | Scheduler | getElevator | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Scheduler | isEnd | 4 | 1 | 0 |
| oo_u2hw2 | (default package) | Scheduler | setEnd | 4 | 1 | 0 |
| oo_u2hw2 | (default package) | Scheduler | isEmpty | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Scheduler | isFull | 3 | 1 | 0 |
| oo_u2hw2 | (default package) | Scheduler | notifyAllPassengers | 12 | 3 | 0 |




从类图中可以看到,这次的结构和上次的结构大致相同,除了中间多了一个ScheduleAll进行总调度,自此我的程序形成了总调度-分调度-电梯运行的三层结构,输入线程将输入传给总调度器,总调度器调度请求给分调度器,分调度器调度请求决定电梯的运行情况,电梯的运行由电梯线程模拟,电梯的状态由电梯类保存。总体来说,继承自第一次作业,复杂度方面和第一次近似,并且留出了一定的扩展空间。
第三次作业
| Project Name | Package Name | Type Name | MethodName | LOC | CC | PC |
|---|---|---|---|---|---|---|
| oo_u2hw3 | (default package) | Elevator | Elevator | 5 | 1 | 3 |
| oo_u2hw3 | (default package) | Elevator | getPassengers | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Elevator | isEmpty | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Elevator | isFull | 9 | 3 | 0 |
| oo_u2hw3 | (default package) | Elevator | getState | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Elevator | getFloor | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Elevator | getRealFloor | 6 | 2 | 0 |
| oo_u2hw3 | (default package) | Elevator | getName | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Elevator | getType | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Elevator | getSpeed | 9 | 3 | 0 |
| oo_u2hw3 | (default package) | Elevator | fresh | 36 | 11 | 1 |
| oo_u2hw3 | (default package) | Elevator | move | 8 | 3 | 0 |
| oo_u2hw3 | (default package) | Elevator | notifyAllPassengers | 15 | 3 | 0 |
| oo_u2hw3 | (default package) | ElevatorThread | ElevatorThread | 5 | 1 | 3 |
| oo_u2hw3 | (default package) | ElevatorThread | run | 43 | 7 | 0 |
| oo_u2hw3 | (default package) | InputThread | InputThread | 3 | 1 | 1 |
| oo_u2hw3 | (default package) | InputThread | run | 38 | 6 | 0 |
| oo_u2hw3 | (default package) | MainClass | main | 6 | 1 | 1 |
| oo_u2hw3 | (default package) | ScheduleAll | addScheduler | 3 | 1 | 1 |
| oo_u2hw3 | (default package) | ScheduleAll | addElevator | 3 | 1 | 1 |
| oo_u2hw3 | (default package) | ScheduleAll | put | 66 | 13 | 1 |
| oo_u2hw3 | (default package) | ScheduleAll | isSameElevator | 3 | 1 | 2 |
| oo_u2hw3 | (default package) | ScheduleAll | canCarry | 12 | 4 | 3 |
| oo_u2hw3 | (default package) | ScheduleAll | setEnd | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | ScheduleAll | schedule | 41 | 9 | 0 |
| oo_u2hw3 | (default package) | ScheduleAll | getEmptyS | 27 | 7 | 1 |
| oo_u2hw3 | (default package) | ScheduleAll | getProperS | 16 | 5 | 1 |
| oo_u2hw3 | (default package) | ScheduleAll | wake | 12 | 4 | 1 |
| oo_u2hw3 | (default package) | Scheduler | Scheduler | 3 | 1 | 1 |
| oo_u2hw3 | (default package) | Scheduler | put | 4 | 1 | 1 |
| oo_u2hw3 | (default package) | Scheduler | getMainRequest | 39 | 9 | 0 |
| oo_u2hw3 | (default package) | Scheduler | judgeOpen | 19 | 5 | 0 |
| oo_u2hw3 | (default package) | Scheduler | getElevator | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Scheduler | isEnd | 4 | 1 | 0 |
| oo_u2hw3 | (default package) | Scheduler | setEnd | 4 | 1 | 0 |
| oo_u2hw3 | (default package) | Scheduler | isEmpty | 3 | 1 | 0 |
| oo_u2hw3 | (default package) | Scheduler | notifyAllPassengers | 15 | 4 | 0 |
| oo_u2hw3 | (default package) | WrappedTimableOutput | println | 3 | 1 | 1 |




分析类图我们发现,这次好像和第二次没啥区别,确实,除了一些方法上的修改和增添,这次的结构和第二次没有任何区别,基本上这次是和第二次保持了一致的架构,对于新增需求,是在各个类内部增加约束条件来完成的。而这次的复杂度除了和第二次保持一致的那几个,put方法也变的复杂了起来,原因是put方法顺带分析了一下该Request是否可以一次到达,如果不是该怎么拆分,这导致了其较为复杂,也缺乏可拓展性。
(4)分析程序bug
只在第三次作业的互测中出现了一个bug,这个bug的出现源于我本地版本控制失误:某方法本应已经加上的synchonized关键字,被我给ctrl + z搞没了,而我也没有查觉到这一点,最终被互测发现。这个bug发生的原因是电梯线程判断电梯是否为空从而决定是否结束,判断电梯为空的方法未同步导致理应结束的电梯线程无法结束,最终tle。
(5)互测bug发掘策略
我在本单元采用的是自动评测的方式,生成测试数据后,利用python在多线程环境下对目标代码进行评测。评测时会按一定规则随机生成900条测试数据,通过已经构建好的ResChecker.py来检查对方的输出是否为WA,或者等待时间是否超过。测试策略的有效性体现在我三次互测均发现了同屋人的bug(也可能是我太菜进C),以及我发现bug后去阅读对方代码发现其对应的设计问题。
对于发现线程安全相关问题的策略,这里我受王鹏博同学启发,选择在多线程环境下进行测试,提高cpu的负载的同时使得多线程问题更容易触发。
本单元的测试策略和第一单元不同主要在于:本单元要考虑多线程问题的存在,有些bug在单线程环境下不一定能够复现,所以采用了多线程的方法进行评测(效果拔群。
第五次:
Archer死于电梯线程无法结束
第六次:
Rider死于电梯震荡
第七次:
Assassin死于电梯调度策略导致电梯震荡
Alterego死于电梯线程谜之消失
(6)心得体会
好的架构给我们带来了好的可拓展性,我终于领悟到这句话的含义了。第一次作业几乎每次都是重构,可能我原来的代码不乏可拓展性,但是至少我没有选择在其上拓展。而这次作业,我第五次作业的架构设计一直延续到了第七次,真真正正让我感受到了拓展带来的效率(第六次花费时间4小时,第七次不到3小时),虽然我的可拓展性、可读性仍然不算好,但这大概算是我第一次尝试整体性的架构规划,(虽然牺牲了一些性能分)。
对于多线程的认识不到位使我在优化的时候束手束脚,什么地方都感觉会引发多线程的问题,我最终放弃了过多的优化(也可能是劣化)。我对多线程的理解是随着代码的书写一步步增加的,但是回过头来看的话,自己最开始写的东西虽然很不让人满意,但是修改成本过大,导致我开始重复“又懂了一点多线程->我刚写的是神魔玩意؟؟”的循环,但回过头来审视,这样的螺旋上升才是学习中的常态。
OO的学习总是苦中作乐,乐中带苦,苦中有苦,乐中有乐;一单元一次的总结使人自闭(不是),希望接下来的作业中我能收获更多吧。
OO_Unit2总结的更多相关文章
- OO_Unit2 关于性能优化与测试的那些事
OO_Unit2 关于性能优化与测试的那些事 OO的第2单元到本周也就正式完结了.尽管这个单元的主旋律是多线程,但"面向对象"的基本思想仍然是我们一切架构与优化的出发点与前提.因此 ...
- OO_Unit2 多线程电梯总结
OO_Unit2 多线程电梯总结 相比于Unit1的表达式求导,Unit2的多线程电梯听上去似乎显得更加"高大上".但在完成了3个task的迭代后再回过头去比较这两个单元,我发现其 ...
随机推荐
- 彻底理解c++的隐式类型转换
隐式类型转换可以说是我们的老朋友了,在代码里我们或多或少都会依赖c++的隐式类型转换. 然而不幸的是隐式类型转换也是c++的一大坑点,稍不注意很容易写出各种奇妙的bug. 因此我想借着本文来梳理一遍c ...
- vue_webpack
1.生成项目工程描述文件 npm init 2.安装webpack开发依赖 (本地安装):npm install -D 3.(webpack4.0版本以上安装webpack cli) npm inst ...
- redis slowlog 慢查询日志
设置 config set slowlog-log-slower-than 10000(微秒) //查看redis时间超过上面设置的阀值的key slowlog len 有几个key slowlog ...
- Vue学习笔记-Vue.js-2.X 学习(七)===>脚手架Vue-CLI(路由Router)
脚手架Vue-CLI(路由Router) 一 按装(通过新创建脚手架按装),如果在原来的脚手架上按装直接进图型化界面vue ui的插件按装. 二 使用(上面按装下面步骤自动会生成) 第一步:导入路由对 ...
- LayUI之弹出层
1.导入插件 layui使用需要导入layui的js和css: <link rel="stylesheet" href="layui/css/layui.css&q ...
- @Transaction注解失效的几种场景
一.@Transactional介绍 1.@Transactional注解可以作用于哪些地方? @Transactional 可以作用在接口.类.类方法上. 作用于类:表示所有该类的public方法都 ...
- Django3.0 + nginx + uwsgi 部署
CentOS7.6 下部署Django3.0应用,使用nginx+uwsgi部署: 1. uwsgi部署 pip install uwsgi 在项目的根目录中,新建文件夹 conf, 然后进入conf ...
- 探索 .NET Core 依赖注入的 IServiceProvider
在上一篇文章中,我们学习了Microsoft.Extensions.DependencyInjection中的IServiceCollection,包括服务注册转换为ServiceDescriptor ...
- C++入门(1):计算机组成
系列文章尽在 | 公众号:lunvey 学习C++之前,我们有必要了解一下计算机的简单组成,毕竟C++是需要操作内存的一门语言.大家或许知道内存是什么,但是内存怎么读取和操作数据以及数据的表现形式会不 ...
- mysql添加远程连接权限
查看登录用户 mysql> select host,user,password from user; 想用本地IP登录,那么可以将以上的Host值改为自己的Ip即可. 这里有多个root,对应着 ...