OO第二单元总结
概述
OO第二单元的主题是多线程电梯,分为傻瓜式电梯,带捎带的电梯,以及多部捎带电梯的协同运作。这是我第一次从单线程到多线程的过渡,不管是多线程的调试还是测试都给我带来了不少的麻烦,但不管怎么说,我在这一过程中还是收货颇多的。
第五次作业
思路: 第一次电梯作业我的架构十分简单,调度器就是队列,开了一个输入线程一个电梯线程,以单例模式建立调度器队列,实现两个线程之间的交互,第一次作业我并没有使用notify和wait方法,而是使用yield类似于暴力轮询的处理方法,毫无疑问这么做导致了较高的cpu时间。这次几乎没有什么调度策略,有人来就先让电梯运输他。
第一次作业比较简单明了,用调度器队列在输入线程与电梯线程之间交互即可,类的作用分离比较清晰,复杂度也不是很高
第六次作业
思路:第六次作业我完全沿用了第五次作业的架构,调度器依然只有一个队列,唯一的不同是调度方法出现了变化,相较于傻瓜式电梯,这次实现了可捎带的功能,增加了电梯运行的效率,采用了指导书提供的方法,先创建一个主请求,剩余请求如果满足条件就进行捎带,在加入的过程中为了进一步提高效率主请求是会不断改变的。这里我觉得电梯的结束条件是比较难判断的,如果像第一次一样使用yield就会导致很高的cpu时间,这里我用wait和notify的时候没有严格保证调度器队列的线程安全从而导致可能出现错误。在运行时的条件判断也考虑不周导致电梯可能出现死循环的情况。
第二次作业几乎沿用第一次作业的设计,唯一的不同就是电梯运行模式的改变,输入线程的接受输入我完全用一个方法来完成,电梯线程的运行我也只开了一个方法,分离度并不理想,move函数的复杂度较高,其实可以将请求再单独开一个类,并进一步细分move操作来进行优化。
第七次作业
思路:第七次作业因为有三部电梯的协同运作,所以我认为单纯的调度器队列已经无法满足要求,因此我对我的程序进行了一次重构,可捎带电梯仅仅根据自己的捎带原则去运行,而调度器线程负责给这三个独立的电梯线程传输任务,调度器的调度原则是先给A,再给B,再给C,因为A速度最快,B其次,C最慢,非常朴素的想法,但是这种调度的话会导致C的运行效率要比A和B低很多,经常处于无事可做的等待状态,在使用JProfile分析时也发现了这个问题,但是因为自己的惰性我并没有去修改这个问题。这里我使用了四个队列,输入线程和调度器之间的队列,调度器和三个电梯线程又各自有一个队列,均使用单例模式创建且保证线程安全。这里我感觉比较麻烦的是各个线程之间的交互,尤其是拆分指令的存在,指定了拆分的前一条指令必须要在后一条指令之前执行,而多线程的执行顺序往往是很难控制的,所以我牺牲了一部分效率,在前一条拆分指令执行完之前不对后一条拆分指令进行分配。
第三次作业创建了三个电梯线程,而且是独立创建,其实这三个电梯的操作有大量相似之处,如果用继承来创建的话应该会简化结构。这次不仅有三部电梯之间的协作,而且由于拆分指令的存在以及各个线程之间的交互,我加了很多控制语句,导致方法的复杂度都比较高,电梯自身的运行流程也比较复杂,在电梯类依然需要自己决策判断指令的执行顺序,可以看出耦合度还是较高,真正理想的设计应该是电梯只管运行,执行顺序全部由调度器来管理,然后自身水平有限,并没有实现这样的架构。
SOLID###
单一责任原则(SRP)
当需要修改某个类的时候原因有且只有一个(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。
开放/封闭原则(OCP)
软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。
里氏代换原则(LSP)
当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-a关系
接口分离原则(ISP)
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
依赖反转原则(DIP)
- 高层模块不应该依赖于低层模块,二者都应该依赖于抽象 2. 抽象不应该依赖于细节,细节应该依赖于抽象
本次作业中,重点考虑单一责任原则和开放/封闭原则。
Single Responsibility Principle:关于这点我认为我三次作业都没有做得太好,电梯线程都不是单纯的只跑,它自身也是有一定的调度器的功能的。
Open Close Principle:我本单元作业也没有很好的实现这条原则,第一第二次作业都没有很好的实现调度器与电梯的分离,导致了第三次作业的重构,第三次作业虽说是有一定的分离,但耦合度还是不理想,可捎带电梯往往还是要去自己决策指令的运行顺序。
BUG###
自己个人的bug这次多出在对细节问题的把控上,第二次作业循环条件的考虑不周导致可能出现死循环,同时对调度器的线程安全保护也做得不周,导致可能出现程序无法结束的情况,第三次作业甚至把电梯最大运行人数写错了,感觉自己在这种细节问题的处理上还有待加强。
关于此次hack别人的策略主要有两种思路,一是增大请求数目,二是在不同的时间点输入ctrlD测试其能否正常退出,由于我不会写自动化测试,只能通过自己构造的方式来测试别人的程序。
总结###
这一单元第一次接触了多线程,还是有很多收获的,尤其是对锁的理解。在第一次作业的时候我直接没有用锁,认为没有必要,第二次作业对锁以及线程的保护也不是很理解,直到第三次我才彻底明白了锁的意义,没有锁不行,但也不能滥用锁,会降低程序的效率,而且一定要理清线程之间的逻辑关系,不然很容易产生死锁,给多线程的调试带来极大的困难。其次是多线程之间的控制,关于wait和notify方面,控制调度不同线程之间的运行逻辑必须细心细心再细心,稍不注意就会产生意想不到的问题。
同时我也认识到了我的不足,对于多线程的调试方面我依然很困难,同时不会做自动化测试也极大的影响了我程序的正确性。另外,三次作业下来和大佬一对比发现每次我的代码架构都比较差,可扩展性都不是很高,如果本单元还有第四次作业的话我可能又要重构。
OO这门课毫无疑问的增加了我的抗压能力和编程设计能力,很多东西看起来简单但实际去做的时候总会发现很多的问题,罗马不是一天建成的,我会紧跟课程组的要求继续探索面向对象这做高山,我相信它在将来会成为我宝贵的财富。