zoukankan      html  css  js  c++  java
  • BUAA_OO_Summary_StageTwo

    一、作业设计分析

    第五次作业

    思路:

    作业要求:

    • 本次作业仅要求一个电梯,基本没有任何限制

    类设计(除去主类):

    • Dispatcher类:继承自Thread,本次作业调度器基本是个空壳子,直接与输入是在一起的
    • Elevator类:继承自Thread,模拟电梯运作
    • Person类:输入的请求转成Person类存放

    线程设计:

    • 我选择使用了两个线程,输入线程和电梯线程

    调度策略:

    • 在看了个各种电梯调度策略之后,发现好像并没有最完美的调度策略,最后选择采取了与ALS较为相近的可捎带策略,稍微做了一丁点的改动

    线程安全性:

    • Dispathcer类设计为线程安全类
    • 考虑在对请求队列遍历过程中可能会有新请求加入,且除此之外不会做其余改动,不会出现其他安全问题,因而在遍历请求队列时未加锁

    一些具体实现:

    • 调度器与电梯共享同一个请求池(请求池本次放在了Dispatcher中,设置了相关的static方法,使得电梯可以直接访问)
    • 电梯在每一层主动检查请求池,电梯调用checkIn()checkOut()方法,检查是否有人要进或者出

    度量分析

    UML图

    各个类的复杂度分析

    • 分别有OCavg(类的方法平均循环复杂度)和WMC(总循环复杂度)两个指标,可见所有指标都没有飙红,复杂度尚可

    总长400行左右

    第六次作业

    思路:

    作业要求:

    • 本次作业要求按照输入的整数,开启3-5台电梯,每台电梯限乘7人

    类设计(除去主类):

    • Person类:仍然是存放请求
    • Pool类:总的请求池,所有的方法和属性均为static,对所有类可见,所有类共享
    • Input类:继承自Thread,输入线程
    • ElevatorStatus类:存储电梯状态,电梯与调度器共享
    • RequestPool类:每个电梯独有的请求池,各电梯间不互通,分别与调度器共享
    • Elevator类:继承自Thread,模拟电梯运作
    • Dispatcher类:继承自Thread,调度器类,负责将总的请求池Pool中的请求分配给每个电梯的RequestPool

    线程设计:

    • 本次作业输入线程和调度器线程是两个独立的线程,除此之外每个电梯一个线程

    调度策略:

    • 电梯的调度策略:基本没有修改,只是将checkOut()方法中的请求池换成自己独有的那个

    • 调度器的调度策略:

      优先将请求分入可以捎带的电梯的请求池中,前提是此电梯请求池的人数加上电梯内的人数小于7

      无捎带的情况下,则优先寻找空电梯

      都不满足的情况下则调用PoolwaitForChange()方法等待,防止轮询,当Pool有新请求加入和任意一个电梯的目标楼层被改变时会唤醒Dispatcher,唤醒后遍历Pool中请求

    线程安全性:

    • ElevatorStatus类、Pool类、RequestPool类均为线程安全类

    线程思路分析

    度量分析

    UML图

    各个类的复杂度分析

    • 电梯和输入类个人认为可修改性不大,调度器的方法在每次电梯状态改变时都会重新调用,导致循环复杂度偏高,还有修改空间

    总长650行左右

    第七次作业

    思路:

    作业要求:

    • 本次作业要求先开启3台电梯分别为A类、B类、C类,并且可根据输入新增对应类型的电梯
    • 不同类型的电梯有不同的可停靠楼层,移动速度和最大载客量

    类设计(除去主类,比第二次作业新增):

    • ElevatorStatusFactory类:在每个ElevatorStatus中存储了电梯的各种信息,为了防止Input的run方法过于冗长,建立此类,根据新加入的电梯ID和类型创建

    线程设计:

    • 本次作业仍是输入线程和调度器线程是两个独立的线程,除此之外每个电梯一个线程

    调度策略:

    • 电梯的调度策略:基本没有修改,只是到了每层之后检查是否可以停靠,如果可以停靠才调用inAndOut()

    • 调度器的调度策略:

      先检查是否需要换乘,如果需要换乘对请求进行修改

      其余部分基本与上次作业一样,只需要检查是否可以乘坐该电梯

    线程思路分析

    度量分析

    时序图

    UML图

    各个类的复杂度分析

    • 本次复杂度基本和第二次没有区别

    总长800行左右

    二、 可扩展性分析

    • 对最后一次作业进行可扩展性分析

    基于S.O.L.I.D.原则:

    SRP原则(单一责任原则):

    • 每个类各司其职,电梯类只负责自己电梯的调度,调度器负责将请求分配给各个电梯

    OCP原则(开放封闭原则):

    • 本单元未进行重构,方法间也没有混杂在一起,基本满足OCP原则

    LSP原则(里氏替换原则):

    • 在本单元作业中未用到继承

    ISP原则(接口分离原则):

    • 本单元作业未使用到接口

    DIP原则(依赖倒置原则):

    • 这一部分不能很好的把握,在编程过程中主要还是对具体类进行编写

    三、Test与Bug

    Bug

    • 在最后一次作业的互测中出现了一个bug,当最后一个人下了电梯准备进行换乘时,电梯停止运行了

    • 但是自己本地跑了上百遍也没能复现出来,无奈之下开始肉眼检查安全问题,最后发现一个很小几率会出现的bug

    public synchronized void getOut(Person person) {//电梯的getOut方法
        SafeOutput.output("OUT-" + person.getId() + "-" +
                          status.getFloor() + "-" + status.getElevatorId());
        if (person.getNeedTrans() && !person.getHasTrans()) {//需要换乘的情况
            person.transfer();
            Pool.addRequest(person);
            status.out(person);
        } else {//不需要换乘的情况
            status.out(person);
            Pool.alert();
        }
    }
    
    public Boolean checkOver() {//调取器的checkover方法
        Boolean over = Pool.getOver() && Pool.isEmpty();
        for (int i = 0; i < status.size(); i++) {
            over = over && request.get(i).isEmpty() && status.get(i).isEmpty();
        }
        return over;
    }
    
    • 由于checkOver()方法的非原子性,倘若只有一台电梯剩最后一个需要换乘的乘客,此时如果checkOver()方法先运行Pool.isEmpty()方法,之后运行了电梯getOut()方法,然后调度器再运行for循环的话,就会误判断此时请求队列和电梯均没有人,而判断应该结束。
    • 针对此bug,由于很难通过加锁的方式将该操作原子化,最终我选择通过更改checkOver()的执行顺序,将Pool.isEmpty()放在最后执行,利用逻辑将其避免

    Test

    由于线程安全问题不一定能稳定复现,我的主要测试是测试WA顺带测试线程安全的问题:

    1. 通过大量的自动生成数据集自动化定点投放输入测试+判定结果正确性

    2. 当发生错误时分析代码逻辑,定位错误原因,再测试出错的一批数据集

    但是自己的线程安全问题没能在课下测出来TAT

    四、心得体会

    • 跟单线程相比,多线程更考验我们的设计以及我们程序设计的严谨性和周密性。

    • 但是也不可为了安全滥用锁,一是锁太多很繁重,对锁的滥用也很容易产生一些死锁等安全问题。

    • 多线程因为不可复现、不可调试的特性,让大家的debug之旅更加痛苦了,尽管我在编程的过程中已经万分谨慎,但是线程安全问题他还是来了。

    • 多线程为了节省CPU的时间一定要用好wait和notify。

  • 相关阅读:
    js中验证身份证号码是否正确支持15位和18位身份证号
    vue-element-admin-i18n 前端框架的使用
    根据年份选择周数-js
    js 计算开始日期和结束日期跨度几个月份的方法
    Java上传图片到服务器
    c# List<Object>和List<实体>相互转化
    GC 相关详细参数
    Groovy脚本和Groovy类反编译文件
    dev 控件中点击TreeList节点高亮显示GridControl中存在的行
    spring容器
  • 原文地址:https://www.cnblogs.com/Kidleyh/p/12698657.html
Copyright © 2011-2022 走看看