一、写在前面:
经过了前三次作业的试水,在第二阶段的三次作业中,因为多线程的引入,面向对象课程进入了需要摸着石头过河的“深水期”。犹记第五次作业发布恰逢清明假期,助教学姐语重心长地在通知板提示同学们早点开始,毕竟此次作业“拿下过往届很多同学的oo课程通宵首杀”。经过我的“实际体验”,多线程电梯的搭建过程实属不易。之后的IFTTT,再到出租车调度系统,都是基于多线程的进一步应用。攻克多线程设计的这个过程中的艰辛自然不必多说,相信每个同学都有着深刻的体会。不过从当初面对多线程的不知所措,再到现如今的基本理解并且可以不错的应用,这一阶段的三次作业确实起到了很大的帮助,其中的所思所想,多线程知识点应用上的认识,就在这里一同诉说吧!
二、第五次作业:
1.多线程的协同和同步控制策略
本次作业实现的是一套由3部电梯组成的多电梯调度系统,参照指导书提示,在本次作业实现过程中我共设计了三种线程——输入处理线程,调度器线程以及电梯线程,并且为了线程间的数据交换设计了托盘。
在托盘中,我设计了共计4个队列,分别对应三个电梯需要执行的指令队列和楼层请求队列。线程间运作的大概实现思路是,输入处理线程将有效输入送入托盘中队列,电梯内请求直接送入对应电梯队列。对于暂时未分配给具体电梯的楼层请求,调度器会不断扫描电梯的状态,在出现符合条件的电梯后,将此请求转移至特定电梯队列中。调度器会分别根据三个电梯的请求队列,再继承上次ALS的调度方式进行电梯调度,将特定指令传达给特定电梯进程执行。
2.度量分析
(类图)
(UML时序图)
(度量图总览)
(度量图部分细节)
分析:从圈复杂度和块嵌套深度可以看出,我的程序在电梯调度和输入处理上显得过于复杂。在实现这些功能时,确实仅仅是在一个方法中实现所有逻辑判断。并且很多流程直接写在run函数中,显得比较冗余,今后可以尝试将相似的功能写成对象内方法,并且精简run方法,提高可读性,减小复杂度。
托盘的数据量确实略多,基本上所有的数据交换都通过了一个大托盘。今后在不同线程数据交换的过程中可以考虑使用多个托盘,避免单个托盘过于复杂的问题。
3.自己程序的bug
01.
由于这是第一次对多线程程序的“试水”,本次程序在多线程调度层面出现了一些bug。具体体现为,在特定情况下,程序的运行结果可能正确亦可能错误。这个bug源自于对线程安全设计的不熟练,在开始设计时并没有得到足够的重视,最终导致了数据冲突等问题的出现,导致错误运行结果的出现。
02.
一种输出情况是,有多条指令在同一时间被执行,除原指令外的输出均相同。这里我想当然的认为同一时间同一个电梯最多仅能够处理两条指令,楼层指令和单个电梯指令。实际上楼层指令又分了上下,所以会出现同一时间处理三条指令的情况,这种情况在设计时并未考虑到,所以出现了调度层面的bug。
4.别人程序的bug
01.
线程运行的不确定性,在相同条件的输入下输出不同结果。这个和我的第一个问题类似,应该源自于对线程安全设计的不熟练。
02.
楼层请求的正确分配。问题应该出在调度器线程对于楼层请求的分配失误。
三、第六次作业
1.多线程的协同和同步控制策略
本次作业实现的是一个监控程序,即根据指令监控文件的变化并且做出相应的反应。除多线程外,我认为本次作业的着重点有二,对文件的处理监控以及线程安全设计。对于后者,我专门实现了SafeFile类对原本File类进行了再封装,使得原本可能发生冲突的类变为线程安全类,从源头上杜绝了数据冲突导致的错误。
整个程序含五种线程——输入处理,监控器,summary输出,detail输出以及用来改变文件状态的测试线程。输入处理线程将合法的输入送入托盘,并且启动相应的监控器线程。对于监控器线程,会不断扫描当前监控文件的状态,与上一次状态进行比对,若发现触发器被触发,则根据上次状态进行还原操作,或者通过托盘向summary或detail传递信息,进行输出。若监控对象为文件夹,每次扫描相当于是生成了包含一个目录内所有文件信息的“快照”,通过前后两次快照的比对进行操作。
2.度量分析
(类图)
(UML时序图)
(度量图总览)
(度量图部分细节)
分析:设计不足基本和上次相似,即线程run方法的冗余和托盘存储的数据量过大。
3.自己程序的bug
由于在进行IFTTT设计的那个星期时间比较紧张,导致本次程序设计出现了一个严重的bug,使得对目录进行监控的功能基本失效。后来证实,是在进行新老快照分享时出现了问题。我使用的是last和now两个数组存储两次快照的所有文件信息,在进行下一次扫描时,进行了”last=now”的操作。这一操作看似是将当前快照赋值到存储上一次快照的数组,实则是将两个数组的头指针指向了同一个位置,导致now和last不论发生什么变化都不会改变,最终导致目录监控的失效。
4.别人程序的bug
在进行测试时我观察到,别人程序在触发器为size-changed,处理方式为detail时,在detail文件中输出文件前后变化时,最后修改时间并没有发生任何改变。原因则在于前后信息的存储输出环节出现了失误。
四、第七次作业:
1.多线程的协同和同步控制策略
本次作业实现的是模拟出租车的乘客呼叫与应答系统。
程序中含三种线程,输入处理,出租车和请求。输入处理线程将合法线程送入托盘,并且启动定时为三秒的请求线程。请求线程从调度器(Tray)获取出租车状态,最终选定合适出租车,推送本请求。出租车按照所处状态以及收到的有效指令信息进行运动,并且和调度器进行数据交换。
2.度量分析
(类图)
(UML时序图)
(度量图总览)
(度量图部分细节)
分析:可以看到,除gui本身可能存在的不足外,圈复杂度和块嵌套深度高的问题集中体现在run()方法,问题大致和之前相同。需要在之后的设计中尽量予以避免。
3.自己程序的bug
在输入处理的时候,对于起始点和终点相同的请求。虽然进行了判错并进行了错误输出,但是忘记了标记flag,导致被当做有效指令被送到调度器,出现错误。
4.别人程序的bug
无
五、心得体会:
这一阶段的三次作业是对Java多线程应用的一个循序渐进的过程,主要体会如下:
- 对于多线程程序,一个好的顶层设计很重要。
- 在实现过程中必须理清逻辑,在必要的地方加锁,避免数据冲突,保证线程安全。
- Debug时使用输出调试法。一个可行的方法是,在输出的时候输出实时状态,监控每一步的变化,这样比较容易找到问题所在。
六、结语:
还记得吴际老师在课堂上画过OO课下作业的难度曲线,这一阶段的作业难度最高,并且在多线程电梯处达到了峰值。情况基本如此,进行多线程电梯和IFTTT设计的过程都是很痛苦的,打车系统也算不上简单。但是,看到自己的程序能够作为一个整体,顺利运行的时候,真的是有一种莫大的成就感。我认为,之所以之后作业的难度不升反降,不是作业变简单了,而是我们在这个过程中不知不觉的收获了许许多多,正像破茧成蝶的过程一样,通过一次次的作业,实现我们的蜕变!
我们仍在途中!加油!
原文地址:https://www.cnblogs.com/buaaidealee/p/8981746.html