第二单元总结性博客

1. 分析程序

1.1 作业一

1.1.1 类图

 

 作业一设计了四个类,电梯类、输入类、主类、队列类。其中主类新建线程和共享对象,输入类、电梯类为两个线程,队列类为两个线程的共享对象。其中队列类和电梯类规模较大。

1.1.2 复杂度分析

 

 

 

 

 用于电梯运行过程中乘客上下和楼层变化,乘客请求调度的函数复杂度高。

1.1.3 流程图

 

 

1.1.4 关于bug

作业一的bug主要是因为对synchronized锁的概念不理解,在wait和notifyAll时出现了死锁,当时是因为想在某个特定的时刻唤醒某个特定的线程,但没有设计好唤醒的时刻导致死锁。关于调度器提供请求和电梯获取请求的两种方式,出现了暴力轮询CTLE。

1.2 作业二

1.2.1 类图

 

 作业二中将作业一中的MainClass和Input合并,有MainClass和Elev两类线程。Reqque为两种类的共享对象,负责所有乘客请求在所有电梯间的调度。Local为每个电梯私有的乘客请求。

1.2.2 复杂度分析

 

 

 

 

 和作业一相似,电梯运行和每个电梯的乘客要求调度复杂度较高。各个类的依赖度较低。

1.2.3 流程图

 

1.2.4 关于bug

第二次作业将Input单独设置为一个线程会出现bug。关于多个线程的关闭出现了问题。

1.3 作业三

1.3.1 类图

 

 作业三与作业二相比,新增一个工厂类,用于构建不同类型的电梯;新增一个类Person,用于储存用户请求和其他相关信息。

1.3.2 复杂度分析

 

 

 

 这次作业复杂度最高的方法是所有乘客请求向各个局部请求队列的调度,这与第三次作业新加的各个楼层限制有关。

1.3.3 流程图

 

1.3.4 关于bug

第四次作业有一组样例使电梯在-3,-4层之间来回反复,产生bug的原因是关于电梯满载只在开关门部分进行判断,没有在判断上下楼层的部分进行判断。关于限定楼层的if-else判断出现了bug,导致电梯在不该开门的楼层停靠开门。关于某部电梯当前请求队列出现了isEmpty的bug。

互测数据采用在某一特殊楼层(只有一种电梯能到达)一次投入大量(超载)乘客。针对边界情况比如-3层和安全性比如满载情况进行测试。

2. 设计策略与可扩展性

 第一次作业的设计策略是每到达一层或每开关一次门就重新从reqque获取一次当前乘客请求。共享对象为reqque,对每个单独的personrequest不需要保护。电梯的当前请求是电梯所在楼层的出门请求、进门请求,可稍带请求,若为空则取最先进电梯的人的请求,若且电梯内没人则取电梯门外最早到来的请求。

第二次作业的设计策略,每部电梯设置自己的局部请求队列,局部请求队列内的调度算法和作业一相同。reqque向每个局部队列提供用户请求,共享对象为reqque,在输入线程和各个电梯线程之间共享。每部电梯在局部队列为空时向reqque发出请求,获得一部分请求队列,队列长度为载客量和reqque当前容量中的较小值。

第三次作业的每部电梯调度策略不变,整体的调度发生变化。每个乘客可以换乘一次,从当前楼层(除1、15层)换到1、15层。每部电梯获取请求队列时,只接受始点和终点都能到达、始点能到达终点不能到达可换乘的乘客。

这三次作业的共享对象都是ReqQue,是需要保护的。而每个PersonRequest同时只在一个线程内存在,因此不需要保护。电梯获得请求需要wait,回写请求(作业三换乘)需要notify,input加入请求需要notify。只要在ReqQue类加锁就可以实现。虽然调度算法的性能比较差,但线程安全不容易出现问题。

 第三次作业的性能涉及比较简单,功能设计和前两次相比也改动不大。为了进一步增加可扩展性,可以把调度功能实现为一个接口,这样可以降低再修改调度策略时对整体程序的影响。产生电梯对象的工厂模式和Person类也增加了程序的可扩展性。

电梯类的开闭原则,第二次作业到第三次作业除了新增类型,还将原来上下楼层的时间等由常数改为变量,在构造时赋值。各个类型的电梯类,除了根据类型赋值,还可以新建三个继承原电梯类的子类。接口隔离原则,电梯的上下行功能与开关门功能互不相关,可以分开实现。共享对象只有reqque,减少各线程之间的交流。

3. 心得体会

三次作业都采用了生产者消费者模式,线程安全的保证比较有条理。在使用ArrayList时要自己保证线程安全,而且对ArrayList的操作都是对引用的操作,如果有需要可以使用clone。在循环中remove时,注意列表内的序号被改变。这次作业没有使用读写锁,因为不太熟悉,全部使用的synchronized块。要注意在共享对象的方法内不要重复调用加锁的函数。测试代码时需要同一个样例多测试几次,减少忽略多线程bug的概率。

在测试样例时,关于如何定时输入花了很长时间,最后采用写一个Test.java用sleep来实现定时输入。在第一二次作业测试时,因为不会构造PersonRequest方法,自己重写了PersonRequest类,为第三次作业时乘客的换乘方法提供了思路。

遵循设计原则可以为后续的程序迭代扩展和保证程序安全提供很大便利。首先线程的实现就是对接口RUNNABLE编程。电梯类如果满足开闭原则,在作业三不同类型电梯时就可以避免大改动。面向对象编程本身就体现了设计原则,我们在编程过程中也要注意满足六大设计原则。

 

posted @ 2020-04-16 10:54  18231002  阅读(149)  评论(0编辑  收藏  举报