BUAA_OO 第二单元多线程电梯问题作业总结

OO第二单元总结

本单元主要通过电梯系列习题培养同学们的多线程编程能力,难度逐步递增。第一次最为简单,第二次、第三次则相对困难。然而我个人在三次作业中第一次作业中使用了相对线程安全的阻塞队列ArrayBlockingQueue,后两次因为作业需求的问题改用本身线程不安全的Arraylist,由程序员本身保证线程的安全性。由于第三次基本是基于第二次作业修改了部分,因此本文重点分析第一次和第三次作业。

一、作业总结及度量

总体设计方面

S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。

SRP The Single Responsibility Principle  单一责任原则
OCP The Open Closed Principle  开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
DIP The Dependency Inversion Principle 依赖倒置原则
ISP The Interface Segregation Principle 接口分离原则
  • 单一责任原则(SRP):这三次作业输入线程仅负责向共享对象中添加需求,电梯线程仅对使用者队列中的需求依次满足,调度器只负责分配指令及。满足SRP
  • 开放性原则(OCP):这三次作业基本上写的函数都需要传入大量参数,可拓展性良好。
  • 里氏替换原则(LSP):这三次作业中,第三次存在着继承关系,在PersonReques的基础上我继承延伸了一个类,即UpersonRequest类。这个类在之前类的基础上拓展了两个方法和一个属性,可以由子类直接替换父类,满足了此原则。
  • 依赖倒置原则(DIP):这三次作业基本实现了抽象与细节的分离,满足了此原则。
  • 接口分离原则(ISP):这三次作业基本以类实现,没有使用接口。

 

 

第一次作业

本次作业可以说是相当简单。基本上直接套用最经典的生产者消费者模型就可以直接搞定。刚开始以为我自己这次作业写的就够少的了,注释空行什么的加起来总共150行不到,然后翻了翻已经提交的同学博客,发现了有的同学一个main类就直接搞定了...总共30行...好吧,是我naive了...(不过我还是觉得不管程序是否简单还是应该按照好的架构写,大二跟大一不一样,不应像大一那样只是不管代码结构多烂,过了评测就好,而是应该培养按照好架构写的习惯)

没什么可分析的,但在这里推荐两本书(老师课上好像也推荐过...我就再提下...)。一本是结城浩写的《图解java设计模式》。这本书相对通读易懂,对多线程还不熟悉的同学可以先看看。另一本书则是网上公认的并发编程经典《java并发编程实战》。这本书很好,但是对于初学者来说真的不是很好懂,反正我个人刚下载下来看这本书的时候蛮懵的...还是建议刚学多线程的同学先看前者,再看后者,循序渐进。

类图:

 

方法图:

 

时序图:

 

 

分析:这次作业没啥好分析的,蛮简单的,总共4个类,我写的也没有错误,就是简单的生产者消费者模式。另外因为代码较少交互情况较少,方法也没有出现耦合度或复杂度较高的情况,较为合理。值得注意的是如果按照ArrayBlockingQueue写的话需要对线程结束方式进行特殊的判断。ArrayBlockingQueue本身是不能加入null元素的,因此无法依据提取的项是否为null来判断结束,而是需要引入一个特殊的判断量来辅助判断是否结束。

 

 

第二次作业

本次作业我修正了底层的实现结构,按照我的算法势必需要对请求队列进行for循环遍历,因此我这次作业之后就开始更换底层的实现结构,将ArrayBlockingQueue更换为线程不安全需要靠程序员进行线程安全性维护的ArrayList。其实写之前也发现可以通过迭代器遍历ArrayBlockingQueue,但因为更熟悉传统的for循环而且在遍历过程中还涉及大量的删除移动等操作及本人想要自己设计下线程安全队列,进一步加深对多线程认识的因素,最后还是选择使用更基础的ArrayList结构,从头到尾通过设计自己保证线程安全性。

 

类图:

 

方法图:

时序图:

 

分析:这次作业我主要追求的是性能的稳定性,对于算法没有设太高的要求,采用的就是每一轮都取队列第一个请求的位置。然后按照第一个队列的方向开始运动。途中遇到相同方向的内容选择添加进后选队列,每到一层判断是否有可以出去的和进来的,然后分别处理。

这个算法的一个大缺点是在每一轮结束到移动到下一轮第一个请求的过程中会忽略一切请求,这使得算法理论上的时间复杂度常数级增加了一倍。然后性能分直接爆炸。

 另外代码风格问题这次floorExecute和Elevator的run函数写的有些复杂。这两个函数分别使用了大量的for循环,并共用了传过来的数据,整体而言并没有满足高内聚低耦合的设计原则。这也值得反省。

 

 

第三次作业

设计框架描述:本次作业我设计了两个请求队列。一个是firstList,所有请求刚由生产者产生的时候都放入这个队列里面。另外有的请求无法通过一个电梯进行访问,因此我又设计了一个secondList,将搭载完第一个电梯还没有到达目的地的请求放入secondList,由调度器进行二次分配。firstList和secondList的请求由主调度器分配到子电梯的请求队列里面,按照优先A,其次B,最后C的原则进行分配。之后各个子电梯按照类似第二次作业的思路进行处理就完毕,这样就实现了对作业难度的降维。

本次作业一开始没感觉有多难,觉得只要把第二次作业稍微改改就好了,但事实却跟我想的截然不同。单纯从结果看,第三次跟第二次代码确实差别不是很大。我第二次写了300行左右,第三次则写了600行,整体架构挺像的,这次只不过额外添加了一个总的调度器和二次处理队列。问题是:整体写作过程十分纠结!!!debug真的de的十分费劲,多线程运行时候好多时候我只能用最简单的println方法进行debug,并不是十分方便。另外在debug过程真的十分纠结,好多时候根本不懂为什么有的函数只是稍微换个形式,明明无论是从时间流程还是执行过程还是大概作用都一样,但就是一个行一个不行。玄学debug...这个问题我到现在都没理解明白,现在还在不断思考。(这个bug会在第二部分bug分析进行介绍)

 

 

下面是类图:

 

 

方法图:

 时序图:

 

 

总结:第三次作业最后写作时还是出现了多个类run方法不够简洁,复杂度和耦合度较高的问题。另外本次作业因为一开始没思考清楚,导致不得不在两个类中同时引入一个类似的方法,降低了代码的优美度(hasItem函数设计的不是很好)。不过总体而言这次程序整体设计还是较为合理简洁的。

 

二、分析自己程序的bug

这三次作业在强测和互测阶段我都没有被发现bug,但在写草稿过中测时基本后两次都是问题不断,一次又一次的调整debug以满足线程安全性。

我写第一次作业时用了阻塞队列结构ArrayBlockingQueue,但为了安全起见又加上了synchronized关键字,结果最后程序死锁了。

写第二次程序时基本只是在第一次作业的基础上添加了for循环,整体没有大变化,基本debug只是在de程序逻辑上的bug。

第三次作业时我主要遇到了一下几个问题:(1)如何让线程在没有指令时等待而不是空转,避免出现CPU超时bug

(2)如何设置结束判断条件

第一个问题部分时候为了避免出现死锁的问题,我选择使用性能有所欠缺但更安全的sleep方法

第二个问题一开始我发现我没有考虑程序执行时的情况,对于多线程的理解不够透彻。最后通过修改synchronized的使用并调整判断条件实现了程序的顺利退出。

第三个问题是因为线程安全的某些问题导致[0.0]秒时无法输入数据,一输入数据就直接退出。这个问题我在debug的过程中盯着看了得1个多小时还是没发现问题在哪里。最后是利用评测机玄学debug,随机修改了一个函数,将其替换为一个作用与写法都极为类似的函数就过了...这也是我这次作业强测没问题互测没问题仍然非常忐忑的问题,这次作业写的太玄学了,总感觉自己对多线程理解太浅,不是很踏实。我之前甚至想过将问题发到博客上让大佬同学们帮着看看。(不过在写博客的时候我又看了一眼,很快就知道了问题所在...没我想的那么严重,我对多线程的理解还行,就是一个简单的时序问题,那时候可能有点着急没看出来,这时候心平气和一细看立刻就发现了。不过确实多线程我还有很多内容需要学习)

 

三、发现别人bug时所采取的策略

这系列作业我基本没用评测机,因为正如吴老师在今天讨论课上所说,随机终究是随机,你不能指望随机对别人的漏洞进行完全覆盖。另外随机还存在着问题:对于多线程程序来说你很难判断出出错的数据是否由同质bug导致。因此这系列作业我采用了不同的查别人bug的策略。

第五次作业,也是多线程第一次作业,代码量较短,而且难度也较小,因此我选择大概看了下他们所有人的代码,看看有没有错。然后随机统一测试几个程序,发现都没问题就不再测了。

 

第六次作业,多线程第二次作业,相对难度有所提升,代码量也有所提升。通读代码开始变成不现实的事情,因此我选择自己手动构造一些覆盖到不同方向的程序(包括我过中测前自己程序错误的一些数据)进行测试,然后通过python程序对结果进行检测。

 

第七次作业,多线程第三次作业,也是是这三次作业最难的一次,虽然感觉自己不太可能去C屋,程序本身正确性没太大问题,同屋其他人程序应该问题也不大,但稳妥起见,我仍然使用了评测机,随机生成数据评测,寻找别人的bug。这次的数据是完全随机生成的,因此我本人并没有办法判断出是否是同质bug(从结果来看,多线程程序可能出错的地方太多了,单个线程本身的设计逻辑有问题,线程间的交互沟通有问题...从C屋的测试结果来看大家错误完全是八仙过海,各显神通,bug基本都不一样,完全没法判断,而且还不好复现...)。

 

心得体会

多线程编程可以说是大多数java程序员的立命之本,即使我们以后不当传统程序员而是走AI向也需要对多线程有着充分的认识,毕竟很多硬件设备和神经网络的训练使用也需要用到这方面的知识。而多线程看起来容易,实际上也是颇有难度的。首先他不好debug...很多时候java多线程的bug很难复现,另外即使bug能够复现在自身对java多线程认识不足的时候也很难判断出到底怎么修改。虽然已经看了《图解java多线程设计模式》,这三次作业我也没有被测出错误,但我还是感觉自己对于java多线程的认识远远不足,回头还需要多看些相关书籍完成些相关练习来加深理解。听说之后两个单元会通过出租车问题加深我们对于多线程的认识,这很让我期待。

最后感谢老师、助教在这个单元给予我们的帮助和指导!

附:

一个介绍UML语法的个人认为较好的博客链接:https://blog.csdn.net/zh_weir/article/details/72675013

猜你喜欢

转载自www.cnblogs.com/super-dmz/p/10746139.html