面向对象程序设计第二单元总结

一、历次作业架构分析

(1)第一次作业

  第一次作业仅有一部电梯,使用简单的生产者消费者模式就可以满足要求。在策略选择上,我使用了指导书中的ALS捎带策略,具体的逻辑是:电梯到达每层时判断是否有可以捎带的请求,以满足可捎带策略。详情可以看第一次作业的UML类图:

 

其中主线程MainClass生成了InputThread和Elevator两个线程,RequestPerson是请求类型的类,WaitQueue是用来存放请求的队列,生产者和消费者就是通过WaitQueue这个桥梁进行存取数据的。在电梯运行时,先选择一个主请求,再考虑可捎带策略:电梯每到达一楼层,都遍历一遍WaitQueue来判断是否有符合捎带条件的请求。

(2)第二次作业

  第二次作业要求多部电梯运行,并允许添加电梯,也就是循序多线程运行以及添加线程。逻辑与第一次作业相同,只是线程数增多了。详情看UML类图: 

与第一次作业的类相同,第二次作业只增加了生成多个线程的部分代码,没有设置专门的调度器,多部电梯相互之间自由竞争,注意电梯线程运行结束的条件。

(3)第三次作业

  第三次作业对电梯规格型号做出要求,不同电梯的运行速度、停靠楼层、最大可搭载人数不同,因此我写了三个类型的电梯线程,每部电梯仍然是相互竞争获取符合该电梯要求的请求。详情见UML类图:

   在代码风格上,这次作业相比于第一单元的作业耦合度没有那么高了,可以按照逻辑将某些功能剥离出来,优化代码结构。代码分析图如下:

 

 

 

 二、线程同步与锁

  正如上面所提到的,多个线程所会用到的共同的数据是用来存放请求的队列WaitQueue,每当线程需要对WaitQueue进行修改和使用时,便在相应的代码模块中加锁。当WaitQueue没有请求时,我们使用wait()等待新请求的到来,相应的,我们在InputThread中设置相应notify()来唤醒如果正在等待的线程,以实现线程的同步。

三、程序BUG分析

  在第一次作业中,由于不理解wait()和notify()以及加锁的含义及用法,在提交测试的时候有好几个点的线程没停下来。在查找相关资料后,发现出错的原因在于把sleep写在锁里面了。当sleep写在synchronized里面的时候,当前线程会休眠,让出cpu,但是synchronized不会解锁,synchronized锁定的方法或者代码块不会往下执行,临界资源别的线程不能用。具体分析和相应解决办法可以参考这篇文章

  在第二次作业中,在强测里出现了两个超时的点,BUG修复过程中发现自己设置的wait()条件是有漏洞的,在某一时刻下添加的请求会导致这个线程一直在wait,修改可以wait的条件就解决了。

  在寻找线程相关的安全问题时,我使用了辅助工具JProfiler查看进程运行的相应情况,示例如下图

 

 多线程BUG分析的不同点也就是在这:第一单元的测试只需要输入数据,然后再验证返回数据的正确性;而第二单元的测试还需要对线程运行有相应地测试,因此这种测试不再是一个黑箱测试,我们需要对其中运行的过程有所了解。

四、心得体会

  线程安全:通过这三次作业,体会到了多个线程在需要利用同一临界资源时,线程之间不能相互产生影响;当没有临界资源可用时,线程应该等待来实现与其他线程如Input线程的同步。

  层次化设计:本单元作业的代码风格比上一单元的代码风格好了很多,主要体现在方法函数的细化、拆分;但同时,主要的逻辑功能还是在Elevator类中实现的,没有能更好地层次化设计。由此体会到,性能和层次设计不能偏颇一方,是需要相互平衡,甚至是不那么矛盾与冲突的,在编写代码时应该仔细分析这些问题。

 

posted @ 2021-04-24 18:41  是茂陵书生  阅读(54)  评论(0编辑  收藏  举报