一、第五次作业——多线程电梯
1.设计考虑
这次主要的数据共享产生在调度器和输入线程共享了输入的请求队列,以及在每个电梯线程和调度器线程各自共享电梯的状态。因此在设计的时候主要要对这两处数据共享进行同步保护。现在看来当时自己设计还是有一定问题的,做到了将输入线程和调度器线程之间的共享对象抽取出来一个队列类,但是在处理调度器线程和电梯线程的时候采用了将电梯的状态存入到电梯线程的做法,这种设计给维护电梯状态的同步带来了比较大的挑战。
除了多线程的同步设计之外,本次作业最大额矛盾点就在于如何在多线程调度花费时间的实际情况下满足指导书要求保证输出的时间不能产生误差。虽然指导书给了100ms的时间误差,但是这个时间间隔对于指令条数比较多的情况下在CPU性能比较差的电脑上就不够了。因此这次为了避免这个误差问题,每次爬楼梯的线程休眠的时间不是固定3000ms不变,而是动态变化的(根据这次调度花费了多少时间相应的就少睡多久,这样基本保证了每次都在不停地消除误差)。
2.OO度量
(1)OO度量
看到主要问题还是出现在方法嵌套层数有点多,以后再写代码的时候会多注意这个问题的,尽量减少嵌套层数。
(2)类图分析
基本设计思路是:Main线程启动输入线程,调度器线程以及电梯线程,然后每次从输入线程读入的请求会加入到请求队列中,调度器线程会扫描请求队列,然后为其分配电梯,给相应电梯设置上下行相关的状态以及目标楼层,然后电梯自己运动。
(3)时序图
设计原则自我反思:这次作业将大量的精力放到了处理共享数据的同步问题,加上此时课堂上还没有对设计原则进行系统地讲解,所以设计的代码风格不太理想。比如Lift类就违背了类的单一职责原则(将线程和共享数据放在一个类里面加大了同步控制的难度),还有现在看来违背了显式表达原则,代码中大量使用常量(应该使用enum类型)。
3.bug分析
(1)自我bug分析
这次作业公测和互测阶段都没有被找出bug,但是在交作业的前一天还是发现了一些自己的问题,并且对这些问题进行了修改。在此之中最致命的一个就是代码有以下两个逻辑语句:一个是将调度器判断完如果指令可以被电梯执行的时候就会设置电梯的状态成为上下行以及同层开门,另一个是设置已经发生空闲等待的电梯的电梯内时间成为请求发出的时间。我最初在设置的时候是按照上面所说的顺序进行执行操作的但是导致最后先设置完状态后直接跳转到电梯线程,导致sleep的时间出现负数报错。最后还是将这两个操作使用锁锁起来才顺利解决这个问题。这个还是对应设计的时候没有完全考虑到线程之间的同步问题,其实关于同步和互斥这两个多线程最重要的问题我直到一周前的操作系统课上老师讲我才真正理解二者之间的区别与联系。
(2)互测bug分析
还是遵守了以前的测试习惯,先将自己在编写代码中留下来的测试数据对对方程序进行测试,发现问题比较明显,于是去定位相关的代码,发现对面的代码在计算输出时间的时候出现了一些问题,并相应针对几个漏洞点进行了相关样例构造以及测试。
二、第六次作业——IFTTT
1.设计考虑
这次作业是第二次进行多线程编程的作业,有了上一次的铺垫,这次对多线程的设计方法有了更深入的理解,对同步控制的运用更加熟练了,因而这次作业的自己也在有意识的向良好的设计原则上靠拢。在设计环节上将每种触发器的3种触发器整合到一个类里面。最开始自己的设计是只有一个触发器对象,里面是许许多多的if-else分支判断不同的触发器类别以及相应的触发动作,导致代码的可读性极差、这个触发器类的复杂度十分高。后来审视了一下自己的设计,发现四种不同的触发器之间有共性的特征,同时也存在不小的差异,因此真正第一次自己设计使用了继承机制(第二、三次作业虽然也有要求,但是都是名义上的假继承,没有任何复用代码和数据),构建了触发器父类以及对应重命名触发器、大小改变触发器,路径改变触发器、最后修改时间触发器这4个类,并真正运用了接口让对应的触发器进行实现。
这次共享对象的同步控制还是比较明显的,在指导书里面基本上已经给出了比较明确的指引方向,主要点就是SafeFile类,我采用了将整个MyFile类的每一个方法声明成静态类,在summary和detail调用写文件的时候必须拿到这个类的锁才能正常写入,这样同步控制是非常简单的,但是同时也牺牲了一定的效率,还有较大的改进空间。
2.OO度量
(1)OO度量图
(2)类图
这次的主要设计思路是:输入线程和主线程先启动,输入线程构造出监视任务(Task类),然后分别启动对应的相关监视线程(分别是4个子类,他们都继承同一个父类),在触发器监视线程中,每个100ms对文件进行一次扫描,并记录下一次快照(FileSnapShot类),然后对比分析,如果需要输出相关内容到文件则使用Detail和Summary类进行输出。
(3)时序图
设计原则反思:这次作业对于设计原则的思考有了很大的进步,第一次自己在真正实践编程过程中意识到了继承和接口机制的真实作用。将几个触发器的通性抽象成一个父类,但是此次还是使用了过多的常量表示状态,还是违背了显式表达原则。
3.bug分析
(1)自己程序分析
本次作业在设计的时候在判断监控对象数目的时候出现了纰漏。出现的问题类定位在了Input类,反思总结最主要的原因是设计的可拓展性不强,导致发现助教对指导书有了新的解释之后自己添加新要求的时候造成代码改动较大,致使出错。最开始自己对于监控对象理解成了一行输入算一个监控对象,但是后来助教解释说是一个文件或者目录算一个监控对象,在改正代码的过程中判断出现了问题。在修改后测试测试到了输入11个文件发现可以正常报错但是忽略了如果输入十个文件但是某个文件有多个监控的情况,导致输入超过10个会被程序误判成超过监控对象数量范围。这个问题暴露的还是设置的时候只顾眼前没有考虑到后期可能产生的修改以及在做完修改后重新测试时没有做到完备测试。
(2)互测bug分析
本次测试的对方代码很简洁、逻辑条理很清晰,没有找到什么bug,但是还是从对面的同学的设计思路上获益匪浅。
三、第七次作业——出租车
1.设计考虑
本次作业的设计思路和多线程电梯的设计思路基本上保持了一致。在老师讲了许多设计原则之后,也针对第五次作业之中许多不合理的设计细节进行了修改。大体思路还是调度器和输入线程共享乘客请求队列,调度器和出租车线程之间共享出租车的状态变量线程。本次设计抛弃了上次的将出租车的状态和出租车线程结合的设计(上次这样设计导致最后同步控制逻辑异常复杂),这次采用了将线程和共享变量互相分离的方法,使得出租车线程里面不需要进行任何的同步控制。
除此之外在设计的时候因为有了更加细致的设计要求指南,所以这次在设计的时候尽可能让自己遵守着课上提到的设计原则。比如为了遵守显示表达原则,将以前定义常量数字来表示状态的做法改成了使用enum枚举类型表示相应的状态。同时查阅并接触了有关面向接口编程的基本思想,开始逐步尝试真正做到使用接口来让自己的程序更加简洁坚固(虽然第二次作业也有要求使用接口,但是那时候的自己真的是为了编接口而编接口)。
2.OO度量以及程序分析
(1)OO度量
看到主要问题还是出现在方法嵌套层数有点多,以后再写代码的时候会多注意这个问题的,尽量减少嵌套层数。
(2)类图分析
类图介绍:
将系统分为以下几种处理方面:a.输入类:按照设计从控制台输入乘客乘车请求或者查询出租车请求,构建输入类InputDeal主要用于输入处理以及将乘客乘车请求添加到请求队列之中。
b.乘客请求类:将乘客抽象成一个请求类Request并构建线程安全类RequestQueue进行请求存储以及删除
c.调度线程类:Scheduler模拟真实的控制中心,做到模拟窗口期以及相应的派单工作,第二个是RequestMap类对应指导书的在请求进入之后进行广播操作(标记某点有请求,让出租车抢单)
d.出租车以及线程类:将出租车的共享变量封装在Taxi类里,并用TaxiThread模拟出租车线程进行运行
e.map类用于初始化地图并在程序开始将所有最短路径进行计算存档
f.测试接口类:提供测试需要的接口
g.辅助类:OutputTestInfo用于输出测试时输入的查询出租车结果,Time类封装了获得时间的方法,发现系统System.CurrentMills()获得时间的速度太慢,查阅资料将System.nanoTime()以及得到对应的模100向上取整的方法封装到了Time类。
(3)时序图
设计原则自我检查:由于有了一个设计原则指南,在设计的时候就要求自己尽可能按照标准规则来设计,这次凡是用于表达状态的都运用了enum枚举类型代替,也将类的职责拆开,尽量做到各司其职,自我感觉比前两次作业的设计有了进步。
3.bug分析
(1)自我bug分析
这次作业被抓到一个bug,原因还是跟指导书理解有偏颇,又没有及时看到issue里所说的。我理解的输出抢单信息是输出抢单时刻对应车的信息(为了判断是否抢单的时候车辆处于请求的辐射范围),但是issue里最后说了输出的抢单信息是窗口期结束的时候各个在窗口期进行过抢单的车辆的信息(主要是判断最后分配时刻是否符合派单的要求)。这提醒自己要定时刷issue。
(2)互测bug分析
本次作业公测主要的测试内容都是对于格式的测试,也从侧面说明了这次测试的难度之大,所以拿到代码常规测试了几个自己在编写代码过程中简单的样例后就开始在读代码的过程中对设计原则进行检查和针对代码进行测试。在读到输入处理的时候检查到对面输入处理的时候有正则处理失误,并发现在选择出租车接单的时候存在调度上的错误,针对这两个读到的问问题构造了bug并上报。在测试过程中并没有发现对面在线程安全方面产生了问题。
五、心得体会
1.线程安全设计:第三次电梯作业是这学期接触的第一次多线程作业,第一次多线程作业是最艰苦的,整整花了一个清明节假期前后很长时间才顺利完成。当时采用的设计以及线程安全控制措施也是非常不成熟的。第一次多线程电梯将电梯线程以及电梯的状态变量相关的共享数据写到了一个类里面,导致类的规模十分庞大,维护线程同步的成本也是比较大。在出租车的时候已经充分意识到了将线程和共享变量类抽离的必要性,并将这种思想落实到了代码之中。
2.本单元的作业自己也开始有意识地在设计的时候遵循设计原则,第五次电梯由于第一次接触多线程,因而大部分精力都投入到了同步控制上,设计上就比较幼稚。第六次作业的时候自己第一次真正体会到继承以及接口在设计时的必要性,第七次作业有了更加详细的设计原则指导,在设计的时候就更加规范。