oo第二次总结

一二.多线程单部电梯

第一次调度由于可以不限制调度方法,所以直接采用的可携带策略。因此,这一二两次作业改动不大,放在一起分析

1.设计策略

因为只有单电梯,所以这两次作业的都是采用两个线程的方式。一个是输入线程,这里设计得比较简单,直接将主线程作为输入线程。还有一个是电梯控制线程。这两个线程由一个需求队列关联起来,输入线程负责将需求放入队列,电梯控制线程负责从需求队列中取出需求,并完成需求。

这是一个基础的生产者消费者模型,其中只有一个生产者和一个消费者,需求队列的容量无限,因为需求队列的无限,所以容量问题并不能对输入线程造成堵塞,所以输入线程无时无刻的监听着需求。对于消费者而言,就要遵守因为队列中没有需求而导致的wait,当然如果没有cpu实际运行时间的限制,也可以使用轮询的方式。因为消费者没有需求进行了wait,所以说在有新输入的时候要注意进行notify,唤醒消费者。特别要注意的一点是,当输入结束时,也要将消费者唤醒,不然处于wait状态的消费者无法正常结束。

关于同步控制这一点,主要是对需求队列的存取问题,这里对所以操作需求队列的方法进行同步。

除此之外,实验中很重要的一点是程序的结束。在这里,我设置了一个标准位,当程序收到结束标志时,将这个标志位置为true,然后输入线程结束。对于电梯控制线程而言,如果读到这个结束的标志位同时电梯以及运作完毕,这就会结束这个线程。至此程序运行结束。

2.类图及程序分析

ChangeReq类负责将需求进行转换,所以比较复杂。Elevator是电梯线程类,类中还包含有电梯实体,以及该电梯的需求队列。

4.uml协作图

程序一共4个类

Main类:负责程序控制,作为主线程还担负着输入线程的功能。

RequestQueue类:需求队列,因为只存在一个实例,所以进行了单例化。在程序最开始就进行了初始化,所以这里并没有保证实例化的线程安全。

Elevator类:电梯的实体,包含电梯的各种属性和方法。

ElevatorControler类:电梯控制线程类,也就是作业中的消费者。其中包含一个Elevator实体和电梯的调度方法。电梯和电梯控制类的设计不够好,很多方法属性没有认真的做区分,到底是属于哪一个类。

程序中有两个最主要的容器,一个是需求队列,一个是电梯内部队列。

说是队列,实际上是采用的map结构,之所以这样使用,是因为在设计中,并没有考虑说需求的先后问题,而是将需求队列转化为按楼层分布的map,这样做的好处是,电梯到达某一楼层后,可以直接访问改map对应楼层的需求,减少因为遍历需求队列带来的时间开销。这样做实践上是会产生一个问题的,比如本应该向上的需求,做上了向下的电梯,但是由于电梯的不限制人数,这样做其实并不会产生消极影响,相反可能还会节省开关门造成的多余时间开销。

同理,在电梯内部的队列也可以采用map,当电梯到达相应楼层,直接将对应的需求输出。两者map的不同之处在于一个是采用进入楼主作为key,另一个采用出去楼层作为key。

最后,电梯采用的调度算法比较简单,到一层就检查一层,有需求就进入,有要出电梯的就出去。当电梯内没有当前方向的需求并且也没有当前方向对应的进电梯的需求,电梯就进行转向。电梯idle比较简单,两个队列都没需求就idle,电梯控制线程wait。

3.度量分析

 

Method metrics

关注几个比较异常的函数。首先是主线程和电梯控制线程的函数,这两个函数·要进行实际的运行,所以复杂度比较高。再者就是队列的查找函数,包括需求队列向上查找和向下查找的函数,以及电梯内部队列向上查找和向下查找的函数,因为需要进行需求的查找进行map的遍历所以复杂度较高。最后是电梯的状态转移函数,电梯要根据实际当前实际状态还要当前需求队列状态进行状体的转移,判断的分支较多,又需要和两个map进行交互,所以复杂度极高。

class metrics

Main类处理担负着主控的任务外,还是作为输入线程,所以复杂度高,应该做到的是两个线程的分离。而ElevatorControler和Elevator之间交互比较平凡,因为对于其属性和方法划分不够细致,所以导致复杂度的上升。

4.uml协作图

5.设计角度问题

基于SOLID原则的评价

SRP(单一责任原则):一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。

在这两次的作业中,对于主控类和输入进程类没有做到分离,而且电梯控制类和电梯类之间耦合太多。

OCP(开放封闭原则):软件实体应该是可扩展,而不可修改的。

关于可扩展的问题,在一开始设计时就预留了一些属性,然而这显然是不够的,应该做到的是使得容易被扩展的模块更加的抽象化。

LSP(里氏替换原则):当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。

作业中并没有使用到继承。

ISP(接口分离原则):不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

作业中未使用到接口。但是对于这个问题,目前阶段还停留在何时去使用接口。我所明白的是接口是对行为的抽象,不知道在这次作业中应该如何使用。

DIP(依赖倒置原则):高层模块不应该依赖于低层模块,二者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象

这块不是太明白,我想要做到的就是尽可能减少类间耦合,对于必要情况进行一定程度的抽象。

6.bug分析

关于结束程序的部分是存在bug的,最开始当输入线程读到结束标志时,是没有notify的,这就会导致处于wait状态的电梯控制线程没办法正常结束。

目前调试bug的方法比较原始,主要是手动输出一些关键信息。

7.作业心得体会

由于第一次作为为第二次作业预留了很多可扩展的地方,所以第二次作业的改动比较小。但是这种可预见性毕竟是有限的,怎样合理化架构,才是真正应该去学习体会的。

. 多线程多部电梯

1.设计策略

这次有多部电梯了,所以前两次没有调度线程的设计,需要改变原有架构。新的架构是,一个输入线程(主线程),一个调度线程,多个电梯线程。输入线程和调度线程由一个队列进行交互,这次真的是一个队列。在需求加入队列前会提前规划好其路径,所以对于调度线程来说,只需要将队列中的需要按照提前规划好的路径放入指定的电梯队列即可。在电梯和调度器之间的队列以及电梯内部的容器依然采用map的设计。

关于同步控制这一点,设计到多个线程交互的情况都需要进行同步控制,除了几个容器外,在电梯类中设计得有一个人数变量(不是电梯内部的人数,而是当前在电梯等待队列人数加上电梯内部的人数总和)。这个变量会被调度线程读取,并作为判断结束的条件,所以存在读写问题,所以也需要进行同步控制。

当然,程序的结束依旧是十分重视的问题。同样设置了一个标准位,当程序收到结束标志时,将这个标志位置为true,然后输入线程结束。对于调度线程而言,如果读到这个结束的标志位同时三个电梯线程人数为0,这就会结束这个线程。电梯线程则以调度线程结束为结束标准。这样设计的目的在于,有些人需要进行换乘,所以从一个电梯出去,并不意味着这个需求的结束

 

2.类图及分析

程序一共7个类

Main类:负责程序控制,作为主线程还担负着输入线程的功能

RequestQueue类:需求队列,因为只存在一个实例,所以进行了单例化。

Elevator类:电梯线程类。由于电梯的实体和电梯控制线程类之间的耦合太多所以直接设计成一个类。

MyRequest类:这是将一个需求进行分段后的存储的数据结构

NewRequest类:为了解决所提供的request类没办法构造的问题。不过后来的新版本有解决。

ChangeReq类:将原有request进行转换的转换类。

Scheduler类:调度线程类。

 

3.度量分析

 

Method metrics

同样关注异常值,Elevator.run()和Scheduler.run()以及Main.main()都属于线程运行的核心函数,所以复杂度高是有理由的,且都在可以接受的范围内。ChangeReq.change()函数,这个函数复杂将原有需求进行转换,因为三部电梯情况有些复杂,特别是第三部电梯,这就需要考虑多种情况,所以说该函数比较复杂。

Class metrics

ChangeReq类负责将需求进行转换,所以比较复杂。Elevator是电梯线程类,类中还包含有电梯实体,以及该电梯的需求队列。

4.uml协作图

5.设计角度问题

基于SOLID原则的评价

SRP(单一责任原则):一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。

主控类和输入进程类没有做到分离,将电梯控制类和电梯类合并成了同一个类,这样类中信息交互比较简单,但是带来的问题是类比较复杂,且违背了SRP原则

OCP(开放封闭原则):软件实体应该是可扩展,而不可修改的。

这次作业相对前两次作业有比较大的改动,之前的设计可扩展性不够,明显,这个规则也违法了。

LSP(里氏替换原则):当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。

作业中并没有使用到继承。实际上,最开始将三类电梯,设计成三个子类,后来发现,其运行的差距可以作为一种属性。比如,禁止停靠的楼层可以用一个list参数传递,这样三个电梯就没有明显区别。

ISP(接口分离原则):不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

作业中未使用到接口。

DIP(依赖倒置原则):高层模块不应该依赖于低层模块,二者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象

依旧不明白。

6.bug分析

同样是关于结束程序的bug。最开始,调度线程结束的标志是输入结束且输入队列不存在额外需求。实际上这是错误的,因为需要换乘的需求在运行完一段后会重新加入需求队列,这时如果调度线程结束,需求将无法完成。

7.作业心得体会

关于性能问题。由于作业中给出的电梯可停靠楼层比较复杂,不好直接进行规划。比较可行的一种方式是,统计电梯负载情况,在有新需求需要规划时,将这一因素考虑进去。

 

心得体会

就线程安全而言,这次实验接触到的模型比较简单,普通的生产者消费者模型。作业中要做到就是,找到多个线程会进行读写的量,然后进行访问控制。当然,如果粗粒度的访问控制将会使得程序性能下降。

除了线程安全外,多线程程序还需要十分注意线程之间的配合,线程何时进行wait,谁有会对线程进行唤醒,合理的设计可以削减cpu的占用时间,设计的不合理,将会导致死锁等问题的出现,需要特别注意。

最后谈一谈设计原则的问题,在这三次的作业中暴露出来的问题还是很大的。在设计时往往只顾眼前利益,缺少统一的规划。在之前的作业中根本没有将这些设计原则考虑进去,在后面的实验中会尝试去遵守这些原则。

posted on 2019-04-22 22:25  wudilailai  阅读(130)  评论(0编辑  收藏  举报