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的迭代后再回过头去比较这两个单元,我发现其 ...
随机推荐
- 「NGK每日快讯」12.3日NGK公链第30期官方快讯!
- java基础学习——Swing图形化用户界面编程
原文链接:https://blog.csdn.net/yiziweiyang/article/details/52317240 链接有详细解释,也有例子,以下是个人参照例子实验的代码. package ...
- 痞子衡嵌入式:自识别特性(Auto Probe)可以让i.MXRT1060无需FDCB也能从NOR Flash启动
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是自识别特性(Auto Probe)可以让i.MXRT1060无需FDCB也能从NOR Flash启动. 接着上篇文章 <了解i.M ...
- 腾讯数据库tdsql部署与验证
环境准备 | 主机 | IP | 配置(最低要求配置) | | :----- | ------------- | ------------------ | | node-1 | 192.168.1.8 ...
- redis数据结构和对象二
跳跃表(skiplist) 跳跃表是一种有序数据结构.跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树简单,所有不 ...
- msfconsole 常用命令记录
Metasploit是一款开源的渗透测试框架,它现在还在逐步发展中,下面介绍的一些功能和命令,可能会在未来失效. Metasploit框架提供了多种不同方式的使用接口: msfgui msfconso ...
- 剑指 Offer 12. 矩阵中的路径 + 递归 + 深搜 + 字符串问题
剑指 Offer 12. 矩阵中的路径 题目链接 题目类似于迷宫的搜索. 需要注意的是,需要首先判断起始搜索的位置,可能有多个起点,都需要一一尝试. 每轮迭代的时候记得将是否遍历标记数组还原为未遍历的 ...
- crontab任务重复执行?不执行?不按照配置执行?大概率是配置出错了!!!
在使用crontab配置定时任务是,容易大意出错的配置记录,有温度的文章分享,有态度的日常记录- 一.情景1 设置每天凌晨执行某一任务,结果发现凌晨0点没分钟都执行了一次,我的天!!! 1.分析原因可 ...
- 主机回来以及,简单的环境配置(RTX3070+CUDA11.1+CUDNN+TensorRT)
紧接着前几天的事: 特殊的日子,想起了当年的双(1080TI)显卡装机实录 和 炼丹炉买不起了:聊一聊这段日子的显卡行情 之后,决定买一台整机玩玩. 而现在,主机终于回!来!了!主机回来干什么,当然是 ...
- 【DB宝42】MySQL高可用架构MHA+ProxySQL实现读写分离和负载均衡
目录 一.MHA+ProxySQL架构 二.快速搭建MHA环境 2.1 下载MHA镜像 2.2 编辑yml文件,创建MHA相关容器 2.3 安装docker-compose软件(若已安装,可忽略) 2 ...