BUAA_OO_HW 8 第二单元博客作业


序言

多线程的程序设计,其实有迹可循。主要要在架构上精心设计,通过优秀的数据结构和轻量化的锁的使用降低代码复杂度,最后从多个方面系统 debug,在评测机确定错误类型后,通过使用 IDE 和编程语言提供的各种工具解决 bug。

然而,我在后两个方面还缺乏系统的训练,投入时间不足,好的架构可以快速学习到,但多线程编程和 debug 的技巧并非唾手可得。如果没有时间的投入,再巧妙的构思也难以正确的变为现实。《后出师表》有言:“高帝明并日月,谋臣渊深,然涉险被创,危然后安。”汉高祖刘邦雄才大略,群臣灿若星河,还要经历艰险,身受创伤,遭遇危难才能安定。我们对多线程的了解本来就比单线程少,倘若还只花费相同甚至更少的时间,不努力学习和理解,经历更多的痛苦,如何更深入地习得多线程编程的技巧,取得更好的成绩?

这一单元的作业留给我学习和思考的无疑太多太多……

hw5分析

hw5 同步块的设置和锁的选择

本次作业的锁全部synchronized锁,主要是模仿PPT和上机练习中给出的方法。由于Strategy是输入和电梯之间的共享对象,主要对Strategy类中的所有方法加锁,使之成为一个线程安全类。在InputData类和Elevator类中设置了同步块。我的同步块和方法设置的比较混乱,感觉应该将同步块尽可能抽象成同步方法,但是这样好像不能解决所有问题,还在持续理解中。

hw5设计

  • 调度器设计:采用随机调度器,将请求编号模6放入对应的电梯中(不敢用过于复杂的调度器,因为害怕出线程安全问题)。
  • 这样的调度显然有极大的局限性,不符合实际生活的情形,在运行时间、耗电量和等待时间上的表现都极为平庸。

hw5 bug分析

由于采用了简单的设计,加之采用了大佬的评测机和print流调试方法,本次作业强测和互测均没有被发现bug。

hw5 UML类图

在这里插入图片描述

hw6 分析

hw6 同步块的设置和锁的选择

本次作业的锁仍然全部使用synchronized锁,但是深入了解了了这种锁的实现原理。特别是掌握了同步块内waitnotify/notifyALL的使用方法。同步块中使用Object.wait()会立即释放锁,而使用Object.notifyAll()/Object.notify()则会等到退出同步块中才执行。

hw6设计

  • 调度器设计:无调度器,自由竞争。当一个请求到来的时候,所有没有接到请求的电梯都去争抢这个请求。之所以要这么写,主要是因为比较简单。这次作业加入了增加和维护电梯的请求,我担心这会对调度器的设计产生很大影响(事实证明是我错了),所以选择了稳妥的办法,争取将这次作业写对,最终也是如愿以偿。
  • 这样的调度仍然有局限性。由于电梯倾巢而出,但最终只有一个电梯能抢到乘客,这会对电量造成极大浪费。

hw6 bug分析

这一次作业我发现我在处理复杂多线程程序bug方面存在很大不足,虽然在这次作业中没有暴露出来(修复bug时间较为充足),但这一致命弱点为本单元最后一次作业的失手埋下了隐患。

hw6 UML类图

在这里插入图片描述

hw7 分析

之前的synchronized锁不变,增加了semaphore信号量,用于控制同一层开门服务只接人的电梯数。同时由于采用了影子电梯的方法,即在分配请求的时候先让每一个电梯模拟搭载乘客,计算出运行时间,取运行时间最小的电梯搭载这个乘客。

hw7设计

  • 调度器设计:设计时间了完备的调度策略,即影子电梯策略。花费三天时间进行重构,最重要是是对电梯上锁,使得其能够获取某一个时间片下电梯的状态,用ShadowElevator进行模拟,将sleep直接换成增加对应的时间,快速得到运行时间计算结果,获取局部最优电梯。
  • 这样的调度比较优秀,但是考虑细节较多,能在大多数情况取得好的性能,但也不可能尽善尽美。影子电梯的仿真特性决定了它是一种优秀的调度,能够在各种电梯性能指标中取得较好的平衡。

这次作业是一次完全的重构,让我对调度器的了解更加深入了。但可惜输在了debug和时间上,分数上受到巨大损失(真的好伤心),但我学到的东西比前两次加起来还多,要是能再多5-6小时的debug,这将是一次很完美的作业。

hw7 UML类图

在这里插入图片描述

hw7 bug分析

这次bug较多。强测错了8个点,互测被刀中两刀(准备蓝桥杯和赶冯如杯没时间了debug了)最为可惜的bug是电梯移动超过两层的bug,这一个bug我现在还没找到很好的架构方法。此外还有偶尔死锁的bug,目前也没有发现很好的解决办法。这可能是由于采用影子电梯后系统复杂度大大增加,加之我多线程的dug能力不足导致的(总的来说还是时间不够)。

总结

本单元三次作业都基于消费者-生产者模型

  • 三次作业中,消费者(电梯)生产者(调度器)产品(请求)的产生形式和功能不断发生变化,越来越复杂,但是它们的协作方法却基本维持不变。在基本模型上,对这三者进行恰当设计就可以很好拓展到更复杂的模式中,但一定要注意确保基本的功能正确(如电梯运行速度,移动层数等)!这是拓展的根基,不容动摇。
  • 第三次作业 UML协作图如图:

sequence graphe

  • 前面说到,个性化需求,如电梯属性是易变 的,但是基本功能,如运送乘客的需求是不变的,这就要求我们在基本架构上不断推陈出新,做出改进。

心得体会

  • 线程安全方面,了解了互斥和死锁的概念,并且深刻认识到了线程协作可能带来的巨大问题。线程写作远没有单线程那么来的直接,必须通过精心的设计,才能满足需要。
  • 层次化设计方面,要求每一个类都各司其职,井然有序。在这一点上我还做的不好。我仍然不太会抽象类的功能和职责,导致大量类存在不必要的协作,耦合度的提高在多线程中意味着灾难。在之后我会更加注意降低类的耦合性,提高内聚性。这方面主要学习了OO度量方LCOMLCOM (类方法缺乏内聚度)。

LCOMLCOM定义如下:
如果类CC定义了nn个方法M1,,MnM_1, \cdots, M_n, 设I1{I_1}是方法M1M_1所用的实例变量集,P={(I1,I2)I1I2=}P = \{(I_1, I_2) | I_1 \bigcap I_2 = \emptyset\}, Q={(I1,I2)I1I2}Q = \{(I_1, I_2)|I_1 \bigcap I2 \neq \emptyset\}

\begin{equation*} LCOM = \begin{cases} \vert P \vert - \vert Q\vert \ if\ \vert P \vert > \vert Q\vert, \\ 0\ else \end{cases} \end{equation*}

LCOMLCOM 越大,类的封装性就越差,类的负荷越重,应该分成几个子类;LCOMLCOM越小,类的内聚度就越大,功能就越专一,封装新就越好。


Author: Yixiang Zhang
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Yixiang Zhang !
评论
  TOC