OO第二单元电梯作业总结

OO第二单元电梯作业总结

概述

三次电梯作业,从实现单部多线程、相同的多部多线程到可以处理增加电梯请求的不同多部多线程电梯,是一个对多线程编程从无到有的指南。

多线程编程的优势和用处不必多说,在OO&OS理论课上老师都已有详尽的阐释。整体而言,在个人学习的体会中,多线程编程的难点集中在:

  1. 多线程概念的理解

    包括:

    • 基本实现
      继承Thread类/实现Runnable接口(我在本单元设计中主要使用Thread类继承。)
    • 线程协作
      放入等待队列:wait()
      出等待队列:notify(), notifyAll(), interrupt()
    • 线程互斥机制
      • 锁与监视机制的理解
      • synchronized方法与synchronized代码块
    • ...
  2. 并发运行时线程不安全问题的解决

    • 避免暴力轮询:对于wait()和notify()的理解
    • synchronized和读写锁read-write lock
    • concurrent包中的线程安全容器
      • ConcurrentHashMap
      • ConcurrentLinkedQueue
      • CopyOnWriteArrayList
      • ...
    • 死锁的处理
      我遇到了对于wait()情况判定过于笼统导致的无限wait,notify不上的情况。修改wait()和notify()条件,使之适配即可。
  3. 电梯调度算法的优化

  4. 多线程程序的调试与测试

    测试方法其实在讨论区和讨论课上各位大佬们都有介绍。列举一下:

    • 断点调试+print
      断点右键suspend,调试界面的Frame和Variables。
      这基本是我本单元最主要的调试工具。
    • JProfiler
      直到第三次作业才开始使用JProfiler(惰性极大=)),结果发现确实好用:使用功能包括试试查看锁和互斥的情况,分析CPU超时的原因,排查死锁等等。
    • ...(其它方式并未有尝试)

    实话实说,测试部分我仅仅停留在能够编写python使批量输入带时间戳的请求,其余的包括自动输出检查、自动构建随机或特殊样例、算法效率分析等相关方面并未涉及;而算法优化我全程使用指导书上的基本算法ALS,对于其他更优的算法仅停留在了解层面,还未实现。

    究其原因,主要是前面两部分的难点在理解和挖掘方面很是迟钝,尤其第二点线程不安全给我制造了较多的困难,并无足够的精力应付测试/算法优化。具体分析在文末反思里。

第五次作业

单线程可稍带,总体难度并不大,核心在于:

  • 对电梯运行过程和捎带算法的理解
  • 多线程入门

UML图

image-20200418083004116

采用生产者-消费者设计模式,输入请求为生产者,电梯为消费者,调度器是仓库。

代码度量

  • 类度量
    image-20200418083708621
  • 类复杂度度量
    image-20200418084344946

第一次作业难度较低也体现在设计架构上的简单。总体来讲,本次架构比较合理,电梯类中集中了较多方法,但都较为内聚,复杂度不高。和第一单元作业相比较能看出较为明显的进步。

出现的bug&互测策略

本次主要问题集中在对ALS算法的不明确,导致性能上的极具下降,强测出现CTLE。

互测时测试自己的中测的所有出问题的数据,未发现别人的问题。

第六次作业

多部可稍带。主要难点在多部电梯线程之间竞争导致的线程不安全。

UML图

image-20200418085344607

本次代码整体架构仍然采用生产者-消费者的设计模式,总体仍是比较清晰且合理的。

代码度量

  • 类度量
    image-20200418085610821

  • 类复杂度度量
    image-20200418090254815

    在复杂度上表现尚可,体量较大的几个方法也都属于可接受范围内。

出现的bug&互测策略

具体来说:

  • wait()和notifyAll()判定条件不适配,导致1. 等待区请求不能notify的死锁 2. 部分请求无法wait(), 持续进入run()暴力轮询,CTLE
  • 电梯类run()中线程结束判定有误,部分电梯出生即死亡,导致的疯狂RTLE

第七次作业

增加了以下需求:

  1. 可在运行时增加电梯
  2. 电梯停靠楼层和运行时间存在差异

主要难度在于对于多种电梯类型的管理和调度

UML图

image-20200418014844743

代码度量

  • 类度量
    image-20200418090459476

  • 类复杂度度量
    image-20200418090551291

    本次在程序架构和代码度量上出现的问题较多。

    架构上面,本来想要采用Worker-Thread模式,设计ElevatorBoss类作为模式中的Master。然而在实现过程中并不能一以贯之,Boss类最终呈现只起到接受增加电梯请求的作用,在实际分派任务时被架空。(所以本质上来说,仍然是一个生产者-消费者模式)

    代码度量上,控制器中的getMain(),电梯类中的Move()方法复杂度较高。究其原因,我在编写时一味地套用了第六次作业的代码,只在其基础上进行修改迭代,导致过分堆叠,代码冗余。应在已有代码的基础上进行架构的调整(重构is everything)。

反思

三次作业,我自己感受和反映在评测里的结果都是相当失败的。每次草草写完程序之后漫长、无章法的针对线程不安全的debug使我在过程中感到无力而又对自己恼火。失败的经验攒了一箱,大致列举如下:

  1. 在动手编程前没有明确具体需求

    比如第五次作业中测时,天真地以为ALS算法只限同层入情况的捎带。这种失误对于整体设计而言是致命的。在动手编程前,务必明确设计要求,最好能够列出提纲,事无巨细的把需求落在实处,在设计过程中逐一实现、后期不断叠加。

  2. 对于自己程序整体架构的不明晰

    debug之路漫漫,其中一个主要原因落在自己每次开始程序编写时脑子里只有整体大致架构,具体的小架构和细节实现依靠编程环节落实;编写完成后,程序在头脑里是碎片化的、是零散的,编写样例进行测试就成了开奖环节,凭空扎针,构建系统而完整的覆盖式的样例测试几乎是天方夜谭。

    其实早在计组P5及之后,我惰于编写样例的问题就已经显露。当时依靠同学之间交换的测试样例勉gou强yan偷can生chuan;但果然,侥幸往往只有一次。

    先设计架构清楚再编写,我再给自己敲一回钟。

  3. 初始对于基础概念的轻视

    多线程编程是对原来单线程的程序设计思路的一次不大不小的颠覆。颠覆必然意味着基础、根本上的改变。第一次作业开始编写时,我对线程协作的概念整体只有一个模糊的认识。对于wait/notify方法、sleep方法以及锁与互斥机制的深入了解都是在程序的一次次问题中获得,可谓效率奇低,给自己无端制造过多障碍。

  4. 惰于编写自动测试程序

    多线程的固有特点导致其测试难度相较于多线程来说显著增大。仅依靠手动输入测试/debug并不可取。然而,也是由于在其他环节浪费较多时间,我并没有进行有效的自动测试/类评测机搭建。事实上,虽然看上去省去(?)了搭建自动测试的时间,实际编写样例、debug环节所花费的时间不知翻了它几倍。既让自己丧失了通过搭建自动测试学习新东西的机会,又效率极低。

跌的跟头多了,至少在失败上有了很多经验;再失败也能迎着困难而上,拍拍土,再从地上爬起来。希望之后的自己能够引以为戒,从教训中获得更多的成长,尽快走出困境。

posted @ 2020-04-18 14:51  aucu1608  阅读(143)  评论(0)    收藏  举报