zoukankan      html  css  js  c++  java
  • 面向对象电梯系列总结

    一. 设计策略

    1. 架构设计

      三个线程:电梯,调度器,主线程(输入线程), 采用worker thread,生产者消费者模式。和同学讨论,发现有的观点认为:调度器更像是一个功能的集合,类似一个函数,不像是一个主体,而且线程越少bug一般而言越少,于是调度器不做为线程。也挺有道理。架构图如下:

       其中task使用ConcurrentHashmap实现,对其方法没有加synchronized, request使用普通List实现, 对其public方法都加了synchronized。这两个类作为被多个线程访问的类,进行了同步控制。

    main向request中放入请求,调度器从request中拿出请求并分配给一个电梯(放进该电梯的task处),电梯从自己的task中拿出请求并执行,car是电梯的轿厢,代表电梯内的请求。此种实现方式不是电梯去向调度器要任务,而是调度器主动分配任务给电梯。整个架构耦合度较低。

    2. 调度算法设计

      将调度分解成两个独立的过程:分配电梯,分配到达楼层。

      分配电梯的算法如下:

     

      其中有容量指task.size+car.size<capacity,电梯只捎带同方向的。

      分配到达楼层:尽力而为,使到达楼层尽可能靠近请求的目的楼层。

      在强测中本人的调度算法表现的还不错,缺点是在较少请求的情况下,没用使负载较为均衡。

    3. 线程如何结束

      只设一个输入结束的全局信号,再根据各个队列是否为空 来决定线程是否结束很容易造成死锁,我采用对象间发送消息的方式来退出线程。main线程在输入结束后通知scheduler线程main已结束,然后结束自己;scheduler判断count(表示已拆分请求个数)是否为0以及main是否结束来结束自己并向电梯线程发送scheduler已结束的消息;电梯线程根据自己的任务队列和轿厢内是否有人来决定是否结束。综上,main退出决定了scheduler退出,scheduler退出决定了elevator退出,整个过程非常有节奏,不再存在有进程没被唤醒或者死锁的情况。

    二. 度量分析

    1. 三次作业的UML图

    2. 以第三次作业为重点,分析经典度量

    类内部的复杂度: 调度器和main类比较重

            

    类间的依赖度:不高

    方法复杂度分析(仅保留值不是很低,起主要作用的方法):调度器分配电梯的函数,电梯运行时判断所在方向是否有请求的函数比较复杂,在情理之中。

    对类与方法的代码规模进行统计:

     

    3. 第三次作业的时序图

    4. SOLID原则

      单一责任原则: 电梯负责运行,调度器负责分配请求,其他辅助类功能都单一,符合该原则。

      开放封闭原则: 第一次作业扩展性不好,导致第二次作业直接重构了,重构后的架构就是上图所示架构,第三次作业在大的方面只修改了调度器以及电梯类的少部分代码。

      里氏替换原则,依赖倒置原则,接口分离原则:程序中没有继承关系和接口。

    三. bug分析

      三次作业均未在强测中出现过bug。

      评测机:写了个python程序来模拟随时间的输入,用脚本构建了一键测试程序,测试的自动化极大地提高了开发效率

      debug: 线程内部的功能bug可通过ide来找,但一旦涉及线程间的交互,就只能使用printf了。当然,熟练使用打印日志的方式后,速度可以比IDE更有效。在code阶段就在关键位置,可能有bug的位置,存在复杂计算,逻辑比较混乱的地方加上打印日志的代码,debug阶段一看日志就能迅速定位错误了。强烈推荐Hansbug写的分级日志输出工具:https://github.com/HansBug/debug_logger,非常好用,极大地提高了打印日志的友好度,真正让我领略到了输出调试的威力

      如果多线程的测试还靠ide, 或者肉眼去看,那可能真会出现”多线程玄学“。当深刻掌握JVM的内存模型,共享对象的可见性和线程的同步性以及使用日志调试后,多线程其实也跟单线程一样,只不过稍微麻烦一些。

    四. 发现别人的bug

      虽然没有互测,但测试自己程序时还是形成了一套方法。

      首先对单个模块进行功能测试,再集成测试。测试用例既要有普遍的,也要有专门针对优化算法设计的,数量要足够多。很多bug都是出现在电梯满了的时候。

    五. 心得体会

    1. 线程安全

      什么是线程安全,如何保证安全大家都懂,我就不赘述了,在这里谈一谈大家容易忽略的问题并推荐几篇文章:

    (1)从Java多线程可见性谈Happens-Before原则(链接)

      在现代操作系统上编写并发程序时,除了要注意线程安全性(多个线程互斥访问临界资源)以外,还要注意多线程对共享变量的可见性,而后者往往容易被人忽略。这篇文章非常清晰地解释了JMM(java的内存模型)中的happens-before原则,读完能够深刻透彻地理解该原则如何解决多线程对共享变量的可见性问题(包括缓存一致性和重排序)。

      虽然可见性问题在这几次作业中我并没有遇到,但应该引起注意。

    (2)关于double-checked locking的讨论

      双检查锁这个技巧看起来精巧,但却是丑陋的,由于JVM的优化越来越完善,在现代工程开发中DCL已经被废弃了。

    2. 设计原则

      UML建模:在进行项目的时候,通过使用 UML 的面向对象图的方式能够更明确、清晰的表达项目中的架设思想、项目结构、执行顺序等一些逻辑思维。这几次的作业比较简单,架构也很明晰,没必要先画出UML图后再去码代码。尽量减少类与类之间的依赖,可以通过消息机制完成类间的通信。

  • 相关阅读:
    Linux软件的卸载
    elasticsearch2.x插件之一:marvel(简介)
    elasticsearch2.x插件之一:kibana
    Jmeter简单测试elasticsearch服务器
    telnet
    koa 路由模块化(一)
    koa 应用生成器
    koa 基础(二十六)数据库 与 art-template 模板 联动 --- 编辑数据、删除数据
    koa 基础(二十五)数据库 与 art-template 模板 联动 --- 新增数据
    koa 基础(二十四)封装 DB 库 --- 新增数据、更新数据、删除数据
  • 原文地址:https://www.cnblogs.com/yifan-liu/p/10754120.html
Copyright © 2011-2022 走看看