• OO第二单元总结


    OO第二单元总结

    设计策略

    第一次作业

      第一次作业结构比较简单,因此我没有使用调度器。刻意练习采用了__生产者-消费者模式__,负责输入的Input类(线程)作为生产者,将请求放入作为传送带(请求队列)的CubbyHole类,电梯类Elevator(线程)作为消费者,直接从请求队列取出请求并根据自己已有的请求设置方向、目的楼层等属性。

      注意这里我没有使用调度器,而是在通过电梯类中的getMajorRequest()方法得到主请求(电梯采用ALS捎带调度算法),再由主请求设置目的楼层。

      对于电梯类的处理,我又在类里面设置了两个队列作为成员变量innerouter,分别储存在电梯中的人的请求和在电梯外等待的人的请求。电梯每移动一层,就检查是否有电梯中的人要出去、是否有电梯外的人要进来。

    第二次作业

      第二次作业与第一次作业的主要差异是多部电梯。为此我新设计了调度器类Sceduler(线程)。我在设计的时候意识到这应该是一个__链状的生产者-消费者模式__,即Input类作为生产者,CubbyHole类作为传送带,Scheduler类作为消费者接收请求,同时又要把请求安排到电梯中去。所以在分配请求的时候,Scheduler类又成了生产者,Elevator类成了消费者。

      就在此时,我的问题出现了。在第二级生产者和消费者,即Scheduler类(线程)和若干个Elevator类(线程)之间,没有加入请求队列。我只是在Scheduler得到一个请求后,就立刻分配到一个合适的电梯去,即调用那个电梯类的outer.add(request)的方法,相当于把请求队列放到了电梯类中。哇,现在看来真的是蠢哭了。老师千叮咛万嘱咐,不要在一个线程中直接调用另一个线程,我还是犯忌了。

    结构_LI

      上图为课程组给出的一种可行结构,我的思路大概预期相同,但是红框部分没有单独写出。我这么蠢的操作偏偏过了强测,于是给第三次作业挖下了一个无底深坑......

    第三次作业

      第三次作业中没有新增类,结构和第二次作业基本相同只是在类中新增了一些方法。

      关于换乘:将请求分为fromFloor-transFloor-toFloor

    • 对于不需要换乘的请求,令transFloor = fromFLoor,特判,直接找一个直达的电梯,将此请求放入电梯的请求队列中。
    • 对于需要换乘的请求:先让人到达transFloor,再让人从transFloor到toFloor。
    • transFloor的选择:我采用的是静态选择,A、B和A、C之间的换乘都安排在1楼或15楼。而B、C之间的换乘则安排在1楼或5楼。

      关于请求的分配:我采用的是无脑平均分配的方式,确实好写233333,不过也确实牺牲了太多太多的性能分。

    线程间的协同

    1. 在第一次作业中,我遇到了这样一个问题:请求模拟器(输入线程)读到了null,于是自动关闭,而电梯还在等待输入,于是程序在处理完最后一条指令之后就一直等下去了而没有及时关闭。讨论区也有学长给出了一个方法——有些类似观察者模式,在请求队列中设置一个对电梯可见的变量isEnd表示输入是否结束,当输入结束时将其置为true;若电梯的请求队列为空且isEnd为true,即电梯已经处理完了当前的所有指令且不会再有指令输入,则电梯线程就可以关闭,这样整个程序就可以顺利结束了。
    2. 关于生产者和消费者之间的协同:生产者向请求队列里放入请求,消费者取出请求,特别要注意请求队列中的线程安全问题,包括在put()和get()加锁、何时wait何时notifyAll等。我曾经遇到过电梯类“长醉不愿醒”的问题,后来发现是少加了notifyAll的缘故。
    3. 关于synchronize:我曾经以为,反正为了保证线程安全,多给几个方法加上synchronize也没什么,但是事实上根本不是如此。加的synchronize太多,那还怎么叫多线程?同时,无脑synchronize还可能导致死锁。个人认为,在一个线程类中,应当减少synchronize块的数量以提高并发性,理想情况下甚至不应该出现synchronize块

    第三次作业架构设计的可扩展性

      第三次作业我出现了__死锁__,我自裁。原因在于线程类直接直接关联,在调度器类(线程)中开辟了电梯类(线程)作为成员变量,结构实在是混乱不堪。调度器在向电梯分配请求时,为了线程安全,会锁住自己并申请电梯的锁;电梯在换乘时又要把当前请求放回到调度器,也会锁住自己并申请调度器的锁——于是死锁产生了。

      个人认为,出现死锁,最靠谱最稳妥的解决方法,只有重构。对于我的问题,就只有在调度器和电梯之间加入一个缓冲队列类,储存分配给该电梯的请求。这样调度器-缓冲队列-电梯又可以构成一个标准的生产者-消费者模式。对于换乘请求,我觉得应该是在电梯类中将请求取出,放回到__模拟请求类与电梯类之间的请求队列中__,由调度器再分配。

      SOLID分析:

    • 单一责任原则:较差。电梯类既负责移动和上下客,又负责给自己找目的楼层。可以给每个电梯安排一个分调度器类。
    • 开闭原则:还行。
    • 里氏替换原则:这次作业里我没有用到类的继承,也就谈不上替换了吧...
    • 接口隔离原则:差。在模拟请求类-请求队列-调度器的这一级生产者-消费者之间还可以,它们的依赖是唯一且单向的。但是在调度器-电梯这一级,无比混乱。如果在中间再加入一级请求队列和电梯状态,就能瞬间解耦、减少依赖。
    • 依赖倒置原则:差。类之间的依赖是具体的,甚至没有实现接口。

    度量分析

    着重展示第三次作业的度量。

    • UML:太丑了,真的太丑了,到底什么时候才能有优美一点的架构啊?

    UML

    • Metrics:MyRequest类中有一个负责找出换乘层的方法,由于是打表所以复杂度高。至于电梯类就真的是无话可说的丑了,因为电梯类承担了许多不属于自己的任务,实际上可以拆成分请求队列、分调度器、电梯三个部分。当前这个奇丑无比的架构勉强能过强测,但必须要重构。等这两天就把它重构了。

    Metrics

    Bug分析

    • 第一次作业中出现了CPUTLE,原因是电梯类的run()中主体为while循环,没有加wait,于是出现了轮询的现象。
    • 第二次作业比较平静
    • 第三次作业的死锁直接翻车,分析在前面。这里我使用java自带的JConsole来分析死锁,能够快速定位到出现死锁的语句并得到此时锁的持有情况。
    • 关于评测机:因为要求分秒不差按时间投放输入,所以手动评测的方法已经行不通了。我是通过Python中的subprocess模块向java程序的stdin写入数据,当然使用还不是很熟练。至于判断输出结果的正确性,我的办法是,写一个程序按照输出的信息模拟跑一遍,只需检查过程中有无非法情况即可。但是我的评测机没办法检测RTLE和CTLE,于是就没查出来死锁......
    • 由于多线程资源分配的随机性,有时会出现bug难以复现的情况,这就比较玄学了。甚至于有时我用printf大法debug,是否输出中间变量都对程序的运行结果有影响——后来经老师和助教点拨才知道,printf会发生中断,从而影响线程的调度。

    总结

    • 多线程啊多线程,你可真是,要我狗命。经过三次作业当然初步体会到了多线程的思维方式,但是使用起来真的感觉很不顺手。
    • 设计模式不只是为了代码的优美,更是为了运行时候的清晰与准确——我当时怎么就把第二级请求队列放到了电梯类里面呢?啊我真的,肠子都悔青了。根本还是在于设计模式的掌握不扎实啊!
  • 相关阅读:
    python 将png图片格式转换生成gif动画
    JetBrains系IDE的设置Pycharm PHPStorm
    Nginx 静态页面POST 请求提示405 Not Allowed
    安装SSL证书 and 根域名跳转www域名
    Linux 安装qt5-designer并集成到Pycharm
    Deepin 自动挂载win NTFS磁盘
    Nginx+DNS负载均衡实现
    swoole websocket服务推送
    Linux版 php5.4 升级php7
    百度 Javascript开发 API
  • 原文地址:https://www.cnblogs.com/Locksoyev/p/12711836.html
Copyright © 2020-2023  润新知