OO第二单元总结

一.设计策略

  这三次作业我都主要采用了生产者消费者模式,输入是生产者,电梯是消费者,他们之间由一个调度器相连,调度器维护一个等待队列储存还未上电梯的乘客,同时将调度器的部分方法用synchronized修饰用于解决等待队列的同步和互斥问题。之后在第三次作业中发现,电梯的乘客队列和主请求虽然每个电梯都是各自拥有各自的,但是也会出现线程安全问题,于是就将电梯的乘客队列和主请求也交给调度器保护,而不新建新的monitor对象,以免造成死锁问题。

二.功能和性能设计

  这三次作业我都是大致采用ALS调度策略。我对于指导书中关于ALS策略的描述的有些细节部分有些疑惑,所以在第一次作业中,我的调度算法写得很简单,以至于在强测的时候运行超时了,这个问题等下在debug部分再详细讨论。在第二次作业中我在第一次作业的基础上,改进了一下算法,扩大了可捎带的条件,主要的运行策略还是没变,还是以主请求为核心运输乘客。并且由于第二次作业提出的要求相对于第一次作业来说需要的改动并不是很大,只要稍微调整细节就可以满足要求。第三次作业时由于各个电梯能达到的楼层不一样,导致各个电梯不仅要修改各自的像可达楼层,速度,负载等属性,电梯之间的协同也更复杂了,多出了很多细节问题。例如只有C类电梯能在三楼停靠,当C类电梯运送的乘客需换乘时,就不能在三楼换乘,要强行改到其他楼层,其他细节debug环节再细讲。

  另外说一下性能部分,性能部分我只对总运行时间进行了较小的优化,对于单独的乘客的等待时间没有考虑,主要是怕算法太复杂时,出现过多的bug,得不偿失。

三.程序结构

这三次作业我采用的相似的程序结构,第一二次作业相对于第三次作业只少了一个工厂类,因为第三次作业电梯较多且属性差异较大,采用工厂模式创建电梯比较合适。第三次作业比第二次作业多了几个小方法,其他都类似。

第三次作业UML类图:

 

 代码度量分析:

 

 

 

 

  这里我惊讶得发现基本复杂度较高的5个方法中有3个分别在3个关于电梯运行的线程中,而这3个关于电梯运行的线程只定义了这一个方法,导致复杂度较高。  

  另外Scheduler.can_add()和Scheduler.change_main()分别是判断乘客能不能上电梯和改变电梯主请求的函数,可能由于判断条件较多导致较复杂,可能可以把部分代码拆分成新的函数。

 

 从上图可以看出电梯类和调度器类承担了大部分的任务,复杂度较高。

线程协作图:

 

 下面对各线程功能做出解释:

Main:创建三个初始电梯线程和输入线程

Input:管理输入

Elevator:启动独属于自己的Change,Inelevator,Moving线程

Change:控制当电梯停靠时间,负责关门和开始运行,即唤醒Moving线程,电梯移动时处于wait状态

Inelevato:控制乘客上电梯,每次开门或者Input接受到输入就被唤醒

Moving:控制电梯的移动,开门之后就进入wait状态,同时唤醒Change线程

其中我考虑过把三个初始电梯线程也放到Input线程启动,但是两者差别不是很大就没有修改。

 

四.bug分析

  第一次作业中,由于我的调度算法写得过于简单,所以没出什么bug,就是运算时间超时了。debug的时候非常痛苦,于datacheck的结果一步一步地比较才发现是算法太简单导致的问题。还有一个bug是我开关门时的输出开始计时之前,导致有时会有停靠时间太短的报错。

  第二次作业相对于第一次作业改动较少,bug也不多,但是我由于没有仔细阅读输入文档说明,在最开始输入电梯数目时自己写了代码提取电梯数量,导致官方包的输入接口没收到电梯数量的信息,错了大片的测试点。

  第三次作业相比于前两次作业会出现很多其他的bug。像是我电梯停靠的算法就出了bug,因为有些楼层只有特定类型的电梯可以停靠,在选取停靠的楼层时就会出现诡异的情况,像是在目标楼层两侧来回跑,但是就是送不到人。还有关于线程的结束也是很难控制,由于电梯能到的楼层不一样,就要求所有电梯同时结束,这就要求已经没有用的线程等着其他线程,再同时结束。由于需要在最后提醒所有线程结束,就有可能造成死锁的情况,我的办法是设置一个变量来标记线程当前的是wait状态还是run状态,如果是wait状态就唤醒,否则就跳过。

  在hack别人时,主要还是根据自己遇到的问题来测别人,用自己在改代码时使用的测试用例,测别人,虽然不知道别人的代码结构我是不是差很多,但是我会用我曾经死锁过的样例和某些有关调度方面的样例进行测试。

 

五.设计原则

  SRP:每个类有自己的责任和方法,像Input类就负责通过调度器向等待队列增加请求,最后输入ctrl+D后判断所有线程能不能结束;Elevator类就负责电梯的运行,不直接于等待队列于Input类相关,信息交互全交给调度器完成,我认为这个原则大致实现了。

  OCP:这三次作业,每次均是在前一次作业的基础上扩展的,但是由于算法的改变每次都有一些修改,而且没有使用继承,接口层次。此原则体现不是很好。

  LSP:此次作业没有使用继承接口,此原则没有体现。

  ISP:没有使用接口,此原则没有体现。

  DIP:由于没有使用接口或抽象类,此原则也没有体现。

 

六.体会

  多线程的操作相比于单线程真的有很大的不同。首先在写代码时,我们只能关注于每个线程单独需要完成什么任务,多个线程访问的共享数据是什么,访问这些数据是会不会造成线程安全问题。这其中对共享数据把握得不是很好,一般来说共享数据越少越好,但有些数据要判断是不是共享数据还是有些困难,我的设计中我最开始任务电梯的主请求不是共享数据,后来运行时发现它被多个线程共享了导致了bug出现。在线程安全方面,我本来想的是只要对共享数据进synchronized保护就好了,后来发现造成了死锁的问题,于是决定减少synchronized的对象,将多个共享数据放到一个synchronized对象下保护。在设计原则方面,总结了一下感觉也太差劲了,以后得好好注意一下了。总得来说,经过这次作业自己的多线程编程技巧有了很大地提升,但感觉自己对多线程的理解还远远不够呢。

posted @ 2020-04-18 11:57  袁昊宇  阅读(125)  评论(0)    收藏  举报