面向对象第二单元总结
面向对象课程进入第二单元,本单元主要训练多线程编程。多线程程序与单线程有很大的不同,有很多需要注意的点。接下来我将从设计策略分析,bug分析以及心得体会三个方面来对我第二单元的作业作一个总结。
1. 设计策略
OO作业是一个系列作业,从最开始定下一个基调是十分必要的。使用什么样的方法,什么思路,运用哪种设计模式,以及注意之后可能的扩展情况。由于我是第一次接触多线程程序,在学习的时候走了不少弯路,最初我想用我自己的方式去设计电梯的调度方式,但十分困难,最终我着力学习了老师上课讲的几种模式(生产者-消费者模式,worker-thread模式,观察者模式等),电梯作业就是按照这几种模式的思路完成的。
第一次作业:
第一次作业只有一部电梯,因此只需要两个线程,Elevator
和 Input
, 两个线程间通过共享对象Manager
进行调度,是标准的生产者-消费者模式。Input
线程读入请求后,存入Manager
中的等待队列,当电梯为空或可捎带,则Elevator
读取请求进入运行队列,由电梯线程执行。
编写多线程程序,线程安全问题是永远绕不开的点。由于本次作业只有一部电梯,只涉及Input
读入请求和Elevator
取出请求执行 ,因此只需要将读入和添加运行请求加锁即可。另一个问题是程序如何结束,我设计了一个Stop
对象,当输入结束时,修改Stop
对象的状态,传入两个线程中,结束执行。
第二次作业:
第二次作业增加了楼层,添加了人数限制,改变了电梯的数量,这就涉及到更加复杂的调度问题。我认为电梯作业的核心在于调度,所以一开始想要保留电梯和输入线程大体不变,对调度器进行修改达到多电梯的目的(继续沿用生产者-消费者模式)。但是在电梯数量较多(4-5部)的情况下,将所有的调度方法都放在一个对象中会十分麻烦,写到最后我自己都分不清哪个电梯在干什么,于是我将Manager
的功能分开,Manager
仅负责调度输入,对于等待和运行队列的管理交给另外两个对象。虽然这一次勉强成功运行,但是我程序的结构变得无比混乱,调度器被拆分成的几部分之间没有条理,这给debug和后续迭代都造成了很不好的影响。在调度策略上,我采用的是先判断是否可捎带,若无可捎带再选择空闲的电梯(这个方案我在设计的时候出现了巨大的bug,放在后面的bug分析中讨论)。
第三次作业:
第三次作业更加复杂,增加了每一个电梯的停靠楼层,这就涉及到换乘的策略。本次作业我的思路是通过拆分请求,比如一个一楼到三楼的请求,如果需要换乘,就将它拆分为1-2
和2-3
两个请求,放入电梯系统去运行。但是由于我前次作业的调度器十分混乱,这一次又需要拆请求,整个调度部分要大幅修改,改到最后让我十分崩溃,不得不放弃这种方式另寻出路,但是时间有限,最终本次作业没有成功提交。
SOLID设计原则检查:
-
SRP-单一功能原则
第二次作业对调度器的处理,本意是想要将复杂的功能分开,但弄巧成拙,导致程序出现严重bug,需要好好总结反思。
-
OCP-开闭原则
在整个迭代的过程中,我十分注意可扩展性的保留。事实上每次作业我确实没有怎么修改原有代码,而是直接在上面添加功能。不过虽然思想上注意到了,但在添加扩展功能时没有条理,这边塞一点,那边塞一点,导致最后程序一团糟,这一点在以后的作业中需要引起注意。
-
LSP-里式替换原则
本单元作业未使用继承,因此略过不谈。
-
ISP-接口隔离原则
同上,本单元作业未使用接口,略过不谈。
其实在第三次作业中我有想过将电梯作为一个接口,使用工厂方法模式生产不同种的电梯,但是最后未能实现。
-
DIP-依赖反转原则
同上,本单元作业未使用接口,略过不谈。
2.bug分析
第一次作业:
第一次作业的bug主要出现在结束程序时。最初我是在每一个线程中都置了结束标志位,然后在传参的时候,manager中的标志位已经更改,但是却没有传入输入和电梯进程(传是传了,但没有反应),整个程序就一直运行停不下来。。后来我就把标志位放在一个共享对象里,通过修改共享对象中的成员变量来达到结束程序的目的(感觉两种方法道理应该是一致的,但前者却总是不成功)。关于互测,本次互测我没有被hack,也没有hack到别人。
第二次作业:
第二次作业的bug较为严重,由于我将原本的manager分离成四个对象,然后在调用的时候过于混乱,导致我的电梯出现吃人的情况。当其中几部电梯满员后,我会先判断是否满足可捎带,然后再选择没有满员的电梯,如果有可捎带就等满员电梯下来人再乘坐,就是在这里经常会出现前一个人下电梯,后一个人上电梯,然后这个人就会消失不见。我认为是运行队列在调度的时候出了问题,前一个人下电梯之后remove会再执行一遍,但我在调整之后依然有一个点没有通过,我到最后也没有找出这个bug在哪里。我推测问题应该还是出在对运行队列的调度上。本次作业没有进入互测。
第三次作业:
第三次作业的问题很集中,是我这几次问题编程的爆发点。由于前几次作业在扩展的时候过于混乱,各种功能的函数被胡乱放置在不同对象中,导致后期修改过于复杂,逻辑不清,整个程序很失败。总结起来有几点:首先关于换乘,我采用的拆请求方法对调度器的设计要求较高,很容易出现诸如后一个请求已经跑了但前一个请求还没到的情况。其次对于多部电梯,我还出现了同种电梯上两次的情况,这个bug主要原因在elevator中,在声明新电梯时将同种电梯放在一个集合中,然后线程运行时同时开启。还有,当电梯种类和数量都上去之后,我的锁也出了差错,程序有时会卡住不动,这个也反映出多线程这一块很多问题我都掌握的不扎实。
心得体会
这一单元的作业我做的可以说很不好。刚开始学习多线程时有些浅尝辄止,很多东西没有弄明白,然后在写代码时就会花费很多时间,一个搞不号好就漏洞百出。而且本次作业还暴露出一个问题:当一个程序的功能较为复杂的时候,我往往处理不好各个模块之间的关系,对每一块的功能没有详细规划,一直都是想在哪加就在哪加,最后debug的时候在各个文件之间反复横跳,效率低下,且极为繁琐。在电梯很多的时候,可以在所有线程上添加一个监视器来帮助管理。在设计架构的时候,一定要考虑功能的集成和逻辑,仔细思考,多动脑后再动手。
我觉得多线程虽然较为复杂,但其实本质上更加实用,如果使用合理能大幅提升性能。虽然本单元作业结束了,但我还要继续总结,继续学习,解决我自己遇到的问题和bug,争取把多线程这一块学透学明白。