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的迭代后再回过头去比较这两个单元,我发现其 ...
随机推荐
- 以代码为剑、数学为犁,SPC构建NGK算力生态体系
人类创造工具,工具反过来也改变着人类.以区块链为核心的货币革命率先吹响了对金融世界重塑的号角.以代码为剑.数学为犁,区块链构建了新的网路信任体系,这是一切的开始.基于此,NGK区块链技术将赋能实体产业 ...
- Azure Functions(二)集成 Azure Blob Storage 存储文件
一,引言 上一篇文章有介绍到什么是 SeverLess ,ServerLess 都有哪些特点,以及多云环境下 ServerLess 都有哪些解决方案.在这众多解决方案中就包括 Function App ...
- 嵌入式开发板使用网口和nfs进行文件共享
如果你的开发板有网口,类似于这玩意. 那么,你可以去买根网线,类似于这玩意. 然后你就可以将你的电脑和开发板用网线连起来,通过nfs(网络文件系统)来进行文件夹共享,文件夹共享就相当于挂载,nfs是利 ...
- 一次"内存泄漏"引发的血案
本文转载自一次"内存泄漏"引发的血案 导语 2017年末,手Q春节红包项目期间,为保障活动期间服务正常稳定,我对性能不佳的Ark Server进行了改造和重写.重编发布一段时间后, ...
- Anno&Viper -分布式锁服务端怎么实现
1.Anno简介 Anno是一个微服务框架引擎.入门简单.安全.稳定.高可用.全平台可监控.依赖第三方框架少.底层通讯RPC(Remote Procedure Call)采用稳定可靠经过无数成功项目验 ...
- 【Notes_4】现代图形学入门——光栅化、离散化三角形、深度测试与抗锯齿
光栅化 Viewport Transform(视口变换) 将经过MVP变换后得到的单位空间模型变换到屏幕上,屏幕左边是左下角为原点. 所以视口变换的矩阵 \[M_{viewport}=\begin{p ...
- RabbitMQ(一)安装篇
1. RabbitMQ 的介绍➢ 什么是 MQ?MQ 全称为 Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.➢ 要解决什么样的问题?在项目中,将一些无需即时返回且耗 ...
- xmake v2.5.2 发布, 支持自动拉取交叉工具链和依赖包集成
xmake 是一个基于 Lua 的轻量级跨平台构建工具,使用 xmake.lua 维护项目构建,相比 makefile/CMakeLists.txt,配置语法更加简洁直观,对新手非常友好,短时间内就能 ...
- Flask-SQLAlchemy使用
Flask-SQLAlchemy 使用起来非常有趣,对于基本应用十分容易使用,并且对于大型项目易于扩展. 官方文档:https://flask-sqlalchemy.palletsprojects.c ...
- rest framework Genericview
通用视图 Django的通用视图...被开发为普通使用模式的快捷方式......他们采取某些共同的习惯和模式的发展观和抽象,从而使您可以快速地将数据写入的共同看法,而不必重复自己发现的. - Djan ...