BUAA-OO-2019 第二单元总结

第五次作业

本次作业,需要完成的任务为单部多线程傻瓜调度(FAFS)电梯的模拟。

设计策略

先来先服务的单电梯是一个标准的"生产者-消费者"模型。虽然在本次作业中调度器似乎是不必要的,但为了更好地应用"生产者-消费者"模型,并方便下一次作业的扩展,还是应该保留了调度器的概念,将其作为"托盘"来存放还未服务的请求。

显然,读取输入并解析为请求就成为了生产者,而电梯就成为了消费者,而在本次作业中调度器其实只起到了存放共享数据的作用,并没有做任何真正意义上的调度。生产者将请求推入位于调度器里的请求队列里,另一边电梯按照先来先服务的傻瓜调度算法将请求一个个取出来并执行。为了保证访问请求队列的线程安全性,推入和取出请求的方法必须用synchronized关键字上锁。

输入和电梯各是一个线程。当请求队列为空时,电梯wait(),直到一个新的请求被推入请求队列,电梯被唤醒。

程序结构

本次作业结构比较简单,一共四个类:输入、调度器、电梯和主类。输入和电梯两个线程通过调度器中共享数据的方式进行通信。

在度量上可以看出整体比较均衡,没有出现个别方法复杂度过大的情况。

关于BUG

记得应该是一遍过了,没有发现自己程序的bug。

菜鸡如我也不会测别人的bug,互测摸了。

第六次作业

本次作业,需要完成的任务为单部多线程可捎带调度(ALS)电梯的模拟。

设计策略

这次的电梯依然只有一个,但要求支持捎带。要实现捎带,就涉及到主请求捎带请求这两个概念。当电梯处于空闲状态时,尝试从请求队列中取出一个请求,作为主请求。在该请求被完成之前,电梯运行路径上遇到的所有目标方向与电梯运行方向相同的请求均被作为捎带请求,存入位于调度器中的捎带请求队列中。当主请求被完成后,从捎带请求队列中取出一个请求,成为新的主请求。

以上取请求的操作依然遵循先来先服务的原则,但有了捎带功能后,某些请求可以被顺带提前满足,电梯的运送效率得到了极大的提升。

设计模式上仍然采用"生产者-消费者"的基本模型,但这次调度器有了些实际作用,用于管理主请求和捎带请求之间的转换关系。

程序结构

由于仍然是单电梯,仅仅是增加了捎带功能,而模型没有变,所以程序结构大体上和上一次类似。

这次代码写得比较急,有些该封装的地方没封装,该复用的地方没复用,所以导致某些方法比较冗长。比如电梯线程的run()方法中,将细节都暴露了出来,有不少重复的语句,现在我自己理解起来都有些吃力。有时间还是应该把代码风格优化一下。

关于BUG

彻底崩盘。要怪只能怪没有做好足够的本地测试(其实也不会测),结果公测和互测都爆炸了,而原因仅仅是从-1层到1层有个地方忘了处理。就因为这一个小细节,整个作业的努力白费,算是一个很大的教训了。下次不能再这么佛系地对待测试,也绝不能对自己的程序抱有莫名的自信。

第七次作业

本次作业,需要完成的任务为多部多线程智能(SS)调度电梯的模拟。

设计策略

终于来了,传说中OO作业的难度峰值。

相比前两次,最主要的区别就是由单电梯变成了多电梯,那么如何处理三个电梯之间的协作关系就成了主要问题。而事实上,所谓的协作关系也只是保证线程安全性,在这个基础上采用电梯间相互争抢的模式,即谁先到达某个请求的出发楼层,谁就服务这个请求。这种策略简单暴力,调度器不需要分配请求,而效率最后被证明也很好。

在电梯调度算法方面,我采用了LOOK算法,并进行了一定的优化:电梯每达到一层,检查请求队列中是否有可以满足的请求,若无则wait(),若有则根据现在电梯内是否有人分两种情况移动:

  1. 电梯内有人,保持原来的运行方向,由下至上再由上至下循环扫描所有能到达的楼层;
  2. 电梯内没人,若当前方向上没有可以满足的请求,则调转方向,否则保持原方向移动。

通过使用wait()和notifyAll(),电梯在空闲时不会无脑疯狂调转方向,从而更贴近真实情况。

此外,本次作业对电梯增加了更多的限制条件,如轿厢容量、可停靠楼层等。对于单个电梯无法满足的请求,需要由调度器将其拆分成两个请求,然后先把第一个请求放进请求队列。这里我利用了HashMap结构,将第一个请求作为键,第二个请求作为值存储起来。每当一个请求被完成后,就在HashMap中查找是否有对应的第二个请求,若有就放进请求队列中。

程序结构

本次作业新增了Request类和Floor类。Request继承了PersonRequest,以实现请求的拆分重新创建,并加入状态位status,用来标记某个请求当前正处于未被服务的状态,还是已经在某个电梯内部了。Floor作为电梯的构造参数之一,作用是管理电梯的楼层信息,包括可达楼层、最高最低层、判断某个楼层是否可达。包括Floor在内的所有电梯属性都可以在主类中显式地修改,避免硬编码,降低耦合度。

我在最后一遍提交前已经把整个程序结构优化了一遍,封装了很多过程,让方法长度尽可能的均衡,然而现在看来貌似还是有些差强人意。这个方面还得再下功夫……

关于BUG

这次我深刻吸取了上次的教训,不会测bug就虚心请教大佬,要来了评测姬。正所谓不测不知道,一测吓一跳。结果果然显示有严重的bug,一个是拆分请求后第二个请求推入时间过早,导致乘客还没从第一个电梯出来就进了第二个电梯;另一个问题是某些情况下电梯wait()后就再也醒不过来了,原因在于notifyAll()的逻辑不太对。

多线程debug的确比较难受,不过好在我向来就习惯用print大法,在每个转向、睡眠、唤醒等关键环节都print出相关信息,所以还是可以比较快地定位到有问题的代码片段。

这样一来强测点稳妥全过,性能分也不赖。互测也很方便,直接用评测姬跑一遍就行,虽然最后只发现了别人的一处bug,果然A组都是大佬orz。

心得体会

这三次作业层层递进的,难度也一次次加大,尤其是第三次的多电梯,对设计架构的要求很高,如果在最开始没有把各个类之间的关系梳理清楚,那么功能实现起来就会重重受阻,甚至面临全盘重构的可能。

然而,这三次作业也是有共性的。对于我来说,输入推入请求,电梯取出请求,这种"生产者-消费者"模型的核心没有变。只要能设计好架构,让耦合度降到最低,那么任意电梯数量、各种限制要求都可以轻松地满足。调度算法也应该封装起来,想用哪种算法单独替换即可,不要因为算法而改变架构本身。

通过这个单元的训练,我对多线程编程有了深入的了解,掌握了线程间通信、同步、互斥的方法,保证线程的安全性。更重要的一方面是,设计架构的能力有了不小的提升,明白如何才能设计出高内聚、低耦合的程序,这对以后可能的企业工作将有巨大的帮助。

posted @ 2019-04-23 15:15 AdamDing 阅读(...) 评论(...) 编辑 收藏