前言

紧张刺激的第二单元结束了,本单元体验极佳,

进行作业前有注重架构的稳定,在良好的架构上考虑优化,如此也便利了迭代。

另外,没遇上线程安全bug的电梯,不是完整的电梯[doge]

HW5

本次作业要求实现单部可捎带电梯,典型的生产者消费者模式,

输入线程Request是生产者,

电梯线程Elevator是消费者,

“托盘”RequestQueue负责协调加锁,

电梯运行模式采用的是look

电梯在可达楼层上下扫描,

当前方向无请求且电梯内无人则掉头,

两边都无请求则等待,

捎带规则是请求与电梯方向(上行或下行)一致且处于同一楼层。

度量分析

Method ev(G) iv(G) v(G)
Elevator.run() 5 6 8
RequestQueue.ifChangedir(int,boolean) 6 4 6
Total 43 65 75
Average 1.54 2.32 2.68
Class OCavg WMC
Elevator 3 27
Floor 1.14 8
MainClass 1 1
Request 1.67 5
RequestQueue 3.4 17
Stop 1 3

本次作业功能较少,复杂度整体不高,控制了每个方法的规模都不大于30行,

标红的只有Elevator.run()和RequestQueue.ifChangedir(int,boolean),

run方法其实已经是只管调用方法的方法了,主要是涉及结束判断以及本身电梯运行动作就很多,

而转向方法主要是为了实现look算法,通过电梯运行方向以及遍历楼层寻找请求返回是否转向标志。

RequestQueue的OCavg也标红,循环复杂度比较高,楼层判空以及电梯转向都是循环+判断,

或许可以单开一个电梯控制类负责电梯行为,分担复杂度。

UML类图与协作图

可以看到,主类只负责创建对象,

RequestElevator实现了Runnable接口,二者通过RequestQueue进行交互,

RequestQueue持有楼层对象Floor,每一层楼用一个Floor模拟,拥有上行和下行两条线程安全队列。

bug分析

  • 自己的bug:本次作业中测阶段发现了一个粗心大意的bug,楼层初始化应该放在电梯启动前,不然会出现大面积NullpointerException,

    强测以及互测未发现bug

  • 屋内其他人的bug:发现了一个人捎带时总会漏一两个人,导致时间比其他人慢一大截,勾起了兴趣,

    仔细研究后,发现在同一时间输入同一楼层请求还可能会少完成请求,具体原因没有找到,

    hack成功一次,屋内一共三刀,以为都是同一个人,结果发现是三个人的bug,

    多线程真是什么奇奇怪怪的bug都有。

HW6

第二次作业加入电梯的数量可以是1-5台,

单部电梯采取的策略还是选择了沿用第一次作业的look,多部电梯每一部一个线程,

除了电梯线程和输入线程外还增加了一个主调度线程MasterScheduler,负责任务的分派。

考虑到如果有多部电梯且电梯还有容量限制

请求集中在少部分电梯上会造成忙闲不均浪费资源,因此负载均衡会取得很好的收益,

所以电梯与主调度器之间还应共享一个电梯状态对象ElevatorStatus,实例化在子调度器中,调度函数依据此来分派请求,以期取得较优效率。

具体来讲,就是根据实际在电梯里的人数,对应楼层已经分派的人数以及电梯获得请求后需要移动的距离作为参考条件计算惩罚值,

惩罚值最小的电梯将获得此请求,

这几个条件还需要加权,经历了调参之后发现电梯人数所占的比例应该稍大一些,最后取得的性能分还不错。

本次作业原本想使用可重入锁ReentrantLock来灵活的进行await和signal,

但是可能是理解得不够透彻,没能顶住例行大数据大范围轰炸,

出现了不可复现的线程安全问题,主要是只出现了一次,没能记录下来,之后干脆synchronized简单明了。

度量分析

Method ev(G) iv(G) LOC v(G)
MainClass.main(String[]) 1 1 35 1
MasterScheduler.dispatcher(PersonRequest) 1 27 45 27
Total 76 128 481 141
Average 1.43 2.42 9.08 2.66
Class OCavg WMC
Elevator 2.89 26
ElevatorStatus 1 9
Floor 1.12 9
MainClass 1 1
MasterScheduler 4.6 23
Request 1.5 6
Scheduler 3.12 25
Stop 1 3
Tray 1.5 9

复杂度相较于第一次不升反到总体上有些下降,良好的设计果然会给编程带来帮助。

这次标红的主要是分派策略上dispatcher方法,也是唯一一个突破40行的方法,

主要因为是计算电梯运送请求距离的时候情况太多,

本次作业也是有意限制方法功能,可以看到方法行数超过30的只有两个方法,

main不算,初始化构造函数太长了没办法

UML类图与协作图

依然采取生产者消费者模式,不过这一次是两级的,

输入与主调度之间有一个简单的共享对象Tray负责协调、

主调度和每部电梯之间都有一个共享对象Scheduler负责协调,这个共享对象实现了子调度器接口ChildScheduler

由于主调度器负责任务分派,因此调度函数实现在主调度器中,电梯对于请求分派不可见,只管跑就完事了。

Stop信号也是两级的,电梯只有在输入线程结束,以及主调度器中请求分派结束、子调度器无请求而且电梯内无人的情况下才能结束。

bug分析

  • 自己的bug:本次在公测阶段没有遇到bug,互测阶段被hack了一个线程安全问题,

    实际上现在我也很疑惑,为什么会在pcj跑出电梯突然中途等了很久又开始运行的结果???

    本地pcj跑了一晚上也没有复现,但是由于这个罕见的bug,我决定肉眼debug,确实发现了两个小概率发生的问题,

    其中一个比较好描述,就是电梯更新状态的时候连续两次调用改变同一状态变量的synchronized方法,

    两次调用之间可能会发生线程切换,将会使用旧的值。

  • 发现别人的bug,这次盲Caster,具体原因读了代码以及本地尝试复现均没有成功发现

HW7

第三次作业最大的不同在于动态加入电梯并且有ABC三种可达楼层不相同电梯,有些请求需要电梯之间的协作才能完成。

这次基调还是生产者消费者思想,受教学视频点拨,又采用了workerThreads模式,

在上一次的基础上将电梯线程创建和启动放置在主调度器MasterScheduler中,

同时为每部电梯实例化一个子调度器Scheduler作为“托盘”。

这次的调度还是秉承着负载均衡的原则,

事先决定A类电梯作为负数楼层和15-20层之间的高速列车,

B类主要负责出发地是偶数楼层的请求,

C类主要负责出发地是奇数楼层的请求,

当然,笔者首先在主调度器中对请求进行一定粒度的分割Dispatch,让目的地在相应电梯的可达楼层范围之内,

每个子调度器在上一次的基础上增加一个nextStage线程安全队列用于储存被分配到的具有依赖关系的请求,

如此一来,换乘问题只需要分割请求、

完成先决请求后回调另外两类子调度器(例如B类子调度器通知AC类)、

进行下一阶段请求这样的系列操作就得以解决。

同类电梯之间也涉及到调度,笔者直接在子调度器复用了第二次作业的调度函数,最终达到的效果也差强人意。

度量分析

Method ev(G) iv(G) LOC v(G)
Scheduler.dispatcher(PersonRequest) 1 28 48 28
Total 111 190 777 250
Average 1.56 2.68 10.94 3.52
Class OCavg WMC
Dispatch 6 18
Elevator 3.79 53
ElevatorStatus 1.22 11
Floor 1.12 9
MainClass 1 1
MasterScheduler 2.8 28
Output 1 1
RequestIn 1.67 5
Scheduler 3.53 53
Stop 1 3
Tray 1.75 7

复杂度有小幅度上升,主要是分派策略是静态的,而且用的都是大面积ifelse,

受其他dalao的启发,其实是可以换成静态数组或者其他容器,直接查询即可。

本次方法规模仍然有意在控制,事实证明控制方法规模有利于定位bug。

UML类图与协作图

从类图中可以看到其实架构相较于第二次并没有太多的改变,

就是多了输出接口的封装以及将分派方法的抽取单独的类,

让主调度器拥有一个单例,也是想到了SOLID原则。



协作图大体不变,只是多了在主调度器以及子调度器两个层次进行的分派

bug分析

  • 自己的bug:本次作业公测、互测阶段均没有发现bug

  • 别人的bug:此次A屋质量可能堪忧???

    屋内b叔的判断超载的条件形同虚设还爆出神奇RE,

    Lancer的电梯会因为线程安全莫名鬼畜导致rtle。

    只有超载是看懂了的,其他的bug恕我能力不足,真的不知道怎么发生的。。。

SOLID原则

  • 单一责任原则

    • 输入线程负责读取输入并输送至主调度器以及向主调度器发送结束信号;
    • 主调度器负责将指令按调度策略分配给电梯、添加电梯并传递结束信号;
    • 电梯从子调度器取指令、向子调度器返回电梯状态以及主动运行。
    • 子调度器充当电梯与主调度器之间的通讯媒介,储存指令以及电梯运行状态。
  • 开放封闭原则

    • 本单元迭代体验极佳,

      除了第二次在第一次的基础上单独开辟一条主调度器线程配备“托盘”通讯外几乎没有改动
  • 电梯类以及子调度器都是从本单元第一次作业复用下来的,通过恰当控制函数就可以使用。
  • 输入线程无过多改动,根据接口对应增加需求即可。

  • 楼层类从第一次作业一直用到了最后一次作业,几乎没有改动。

  • 第三次作业的电梯状态类原封不动地源自第二次作业

  • 里氏替换原则:本单元没有用到继承。

  • 接口分离原则:线程实现Runnable接口,Scheduler实现ChildScheduler接口,没有臃肿的接口。

  • 依赖倒置原则:

    • ElevatorMasterScheduler依赖于负责通讯的实现ChildScheduler接口的Scheduler

      如果遇到新需求需要新种类子调度器的话可以减少改动。

感想

本单元感想就是一个字儿,爽!!

课程视频助我探索多线程的设计模式!!!(迭代过程思路清晰,基本周二晚上可以出第一版

考虑优化助我走出舒适区!!!(虽然做的也不是很好了

莫名死锁助我加强测试、push我深入代码的字里行间进行探索!!!

评测组强大的pcj助我push别人修神必的锅!!!

OO与知乎上所说的“远古”OO已经大不一样了,希望OO越走越好!!!

OO电梯系列总结与反思的更多相关文章

  1. OO电梯系列优化分享

    目录 前言 HW5 HW6 第二次作业uml协作图 HW7 第三次作业uml协作图 前言 本单元作业在优化方面确实有一些想法值得分享,故单开一篇博客分享一下三次作业的优化以及架构. 三次作业的共同之处 ...

  2. OO第一单元总结与反思

    OO第一单元总结与反思 目录 OO第一单元总结与反思 摘要 第一次作业 本次作业UML类图 本次作业度量分析 第二次作业 本次作业的UML类图 本次作业的度量分析 第三次作业 本次作业的UML类图: ...

  3. 电梯系列——OO Unit2分析和总结

    一.摘要 本文是BUAA OO课程Unit2在课程讲授.三次作业完成.自测和互测时发现的问题,以及倾听别人的思路分享所引起个人的一些思考的总结性博客.主要包含设计策略.代码度量.BUG测试和心得体会等 ...

  4. BUAA_OO_电梯系列

    电梯作业 第一次作业和第二次作业 由于我第一次作业给傻瓜电梯写了捎带所以第一次第二次作业差不多 电梯运行一个线程Elevator,输入控制一个线程Call 一个物理电梯控制表可以完成移动和进出人功能, ...

  5. 学会拒绝,是一种智慧——OO电梯章节优化框架的思考

    在本章的三次作业里,每次作业我都有一个主题,分别是:托盘型共享数据.单步电梯运行优化.多部电梯运行优化,因而电梯优化实际是第二.三次作业.虽然后两次作业从性能分上看做得还不错,但阅读其他大佬博客,我深 ...

  6. OO电梯调度

    告别了三次奇妙无比的求导作业之后,我们就开始搭建一部自己的电梯了.相信我们不同同学的电梯运行方式肯定各具特色吧,但值得肯定的是,在艰苦的走完了三次电梯逐步改进的作业之后,我们的电梯在正常情况下应该是可 ...

  7. OO——电梯作业总结

    目录 电梯作业总结 程序结构与复杂度的分析 第一次作业 第二次作业 第三次作业 程序BUG的分析 互测 自动评测 有效性 总结 电梯作业总结 程序结构与复杂度的分析 第一次作业 1.设计思路 第一次作 ...

  8. OO电梯作业总结

    (一)第五次作业 一.设计思路 生产消费者模型,输入接口是producer,调度器是tray,电梯是customer.由于只有一架电梯,所以生产消费模型满足以下条件: 一个生产者,一个消费者 托盘不为 ...

  9. OO第二单元电梯线程系列总结作业

    电梯系列第一次作业 功能描述: 傻瓜电梯无需考虑超载捎带 线程模式: Producer-Consumer Pattern 思路: 第一次作业是一个傻瓜电梯,分别有一个生产者生成电梯指令(也就是Inpu ...

随机推荐

  1. K8S部署Redis Cluster集群

    kubernetes部署单节点redis: https://www.cnblogs.com/zisefeizhu/p/14282299.html Redis 介绍 • Redis代表REmote DI ...

  2. JUnit5学习之三:Assertions类

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. Ping 的工作原理你懂了,那 ICMP 你懂不懂?

    计算机网络我也连载了很多篇了,大家可以在我的公众号「程序员cxuan」 或者我的 github 系统学习. 计算机网络第一篇,聊一聊网络基础 :计算机网络基础知识总结 计算机网络第二篇,聊一聊 TCP ...

  4. 获取点击元素的id

    1.onclick="dianji(this.id)" 传入id到方法里function dianji(id){ //这个就是id}2. $(document).click(fun ...

  5. window下象MAC一样工作的工具

    前面是MAC 后面是windows对应工具,只是做一个列表说明,具体使用自行百度 1.item2 vs Cmder 命令行 2.Homebrew vs Chocolatey 包管理器 3.Spotli ...

  6. Jenkins+Python自动化测试持续集成详细教程

    ​ Jenkins是一个开源的软件项目,是基于java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能.由于是基于java开发因此它也依赖jav ...

  7. Centos7安装Docker&镜像加速

    目录 Docker Docker安装 方式一 方式二 docker 镜像加速 Docker Docker安装 Docker安装 方式一 step1: 删除老版本(Uninstall old versi ...

  8. 剑指 Offer 38. 字符串的排列 + 无重复元素的全排列

    剑指 Offer 38. 字符串的排列 Offer_38 题目描述 解题思路 可以使用递归实现全排列,每次都确定一个数的位置,当所有位置的数都确定后即表示一个排列. 但是考虑到本题需要排除重复的排列, ...

  9. CSDN博客转MD格式

    基于大神作品修改原文,使用了一下发现有一些小问题,爬取的博客标题如果含有字符是Windows不支持的命名格式,会卡在界面,进行了一下优化,加了一些字符过滤处理,但是tomd模块对html的处理还是不是 ...

  10. 精确率precession和召回率recall

    假设有两类样本,A类和B类,我们要衡量分类器分类A的能力. 现在将所有样本输入分类器,分类器从中返回了一堆它认为属于A类的样本. 召回率:分类器认为属于A类的样本里,真正是A类的样本数,占样本集中所有 ...