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的迭代后再回过头去比较这两个单元,我发现其 ...
随机推荐
- Flutter 在同一页面显示List和Grid
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends State ...
- Dart: puppeteer库
和node的差不多,只有写API不一样 puppeteer 地址 安装依赖 dependencies: puppeteer: ^1.7.1 下载 chrome-win 到 <project_ro ...
- TypeScript——02——TS基本数据类型介绍和使用
一,TS的数据类型 ES6的数据类型: 6种基本数据类型 Boolean Number String Symbol undefined null 3种引用类型 Array Function Objec ...
- 人物传记-BILL RAY:低谷时的信念,决定你能走多远
自2018年全球经济危机以来,以工作为重的成年人们一直备受打击.尤其是2020年,全球贸易争端使得经济下滑严重,很多公司倒闭破产,有些人甚至从富豪变成了负债者,走向了人生低谷.其实,每个人都会遇到人生 ...
- OpenCVE-开源漏洞预警平台
0x01简介 主程序主要是通过使用NVD提供的JSON数据来更新CVE数据,并在前端进行展示.然后通过邮件进行通知,目前也只支持邮件.这个开源预警平台看上去并不是很完善,因为CVE本身就具有预警滞后性 ...
- [转]Linux下scp的用法
http://blog.51cto.com/yaksayoo/175719 scp就是secure copy,一个在linux下用来进行远程拷贝文件的命令.有时我们需要获得远程服务器上的某个文件,该服 ...
- 1053 Path of Equal Weight——PAT甲级真题
1053 Path of Equal Weight 给定一个非空的树,树根为 RR. 树中每个节点 TiTi 的权重为 WiWi. 从 RR 到 LL 的路径权重定义为从根节点 RR 到任何叶节点 L ...
- 设置mysql的字符集永远为UTF-8
1.在虚拟机/usr路径下创建一个文件命名为:mysql.cnf cd /usr touch mysql.cnf 2.在该文件中使用vim命令插入配置文本 vim mysql.cnf 按i键进入编辑模 ...
- 最常用SQL joins:内连接(交集)、左外连接、右外连接、左连接、右连接、全连接(并集),全外连接
1.内连接.两个表的公共部分用Inner join,Inner join是交集的部分. Select * from TableA A inner join TableB B on A.key=B.ke ...
- NodeJs 入门到放弃 — 入门基本介绍(一)
码文不易啊,转载请带上本文链接呀,感谢感谢 https://www.cnblogs.com/echoyya/p/14450905.html 目录 码文不易啊,转载请带上本文链接呀,感谢感谢 https ...