标签:全局 同步 moni hand lock input 总结 相关 状态
自从OO的作业进入了多线程时期起,我感觉自己和周围小伙伴的睡眠开始变得明显不足了。打从我们一开始接触多线程设计起,我们就对这种不同于以往编程的玄学程序抱有极大的不满。类似于这次结果不对,把IDE重启再跑一遍结果就对了的例子数不胜数。不过,好在随着我们对多线程玄学的研究慢慢加深,我们对多线程程序设计方法的认识更加深入了,于是在这里对前三次多线程作业进行一个简短的总结。
关于对自己程序的分析与度量。关于三次作业的Metrics度量如下图所示:
第五次作业
第六次作业
第七次作业
类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模如图所示
三次作业的类图如下图所示:
第五次作业
第六次作业
第七次作业
第五次作业的类图优点是Dispatcher类,InputHandler类与RequestQueue类之间有良好的协作,个各类之间的结构分工较为明确;缺点是Dispatcher类的继承太过冗余,变量的定义过于繁琐,定义了很多用处不大,但是很消耗内存的成员变量。第六次作业类图的优点是4个Trigger类,两个Mission类分工明确,TestThread类和SafeFile类结构紧凑,整个类形成了以MonitorJob类为核心的网络;缺点是类的数量较多,类之间的总体关系较为繁琐。第七次作业的优点在于类的数量十分精简,Dispatcher类,RequestQueue类,InputHandler形成了标准的生产者-消费者系统,Taxi类与Request类的结构也很清晰;缺点是Dispatcher类较其他类过于庞大,不是非常平衡。
三次作业的UML协作图如图所示:
第五次作业
第六次作业
第七次作业
关于这三次作业中设计方面出现的问题和不足,在第五次作业中,主要体现在调度器从前两次作业的继承过于鸡肋上,没有很好的利用继承得到的资源,而是自己重写了很多方法,设了很多新变量,最后导致Dispatcher文件非常的大。在第六次作业中,四个Trigger类的结构过于相似,每个Trigger都单独实现的话会多出许多冗余的代码,应该先实现一个基类再进行继承。在第七次作业中,求最短路径函数的重复使用造成了某些无法解释的线程问题(因为用一次求最短路径函数的消耗较大),应该在设计时注重数据的重复使用。
第五次作业是多线程电梯,也是我们的第一次多多线程作业,这次作业里面我的线程类有三个,分别为调度器类,电梯类,以及输入类,实例化的对象有五个(构造了三台电梯)。线程保护的类有一个,即RequestQueue请求队列类,因为在程序运行的时候RequestQueue对象就是一个托盘,生产者InputHandler对象不断地向其中加入新的请求,而消费者Dispatcher不断地从其中拿出请求进行处理。在程序运行时,五个变量同时运行,共五个线程,Dispatcher对象不断地从请求队列中拿出由InputHandler加入的请求对象,然后根据请求内容相应地改变三部电梯某些变量的值,从而控制三部电梯运行。在第六次作业IFTTT中,共有四个线程类,分别为监视器类Monitor,两个任务类recordSummaryMission和recordDetailMission,以及测试类TestThread。在程序执行时,先根据输入的监控任务构建对应个数的Monitor对象并挂起,然后再挂起TestThread对象,由其对文件进行某些改变,Monitor不断扫描这些改变,一旦发现某些变化,就改变recordSummaryMission或recordDetailMission对象中的某些全局变量,使这两个任务类做相应的任务操作。线程安全类有SafeFile类,其对文件的读与写进行保护,防止边读边写或者边写边读的易发生错误的情况出现。第七次出租车作业中,有三个线程类,分别是输入类InputHandler,出租车类Taxi,调度器类Dispatcher。共构建了102个对象(因为有100辆出租车)。有一个线程安全类RequestQueue,其效果和多线程电梯中的RequestQueue类似。程序运行时,同样是由Dispatcher对象对请求做一定的处理,然后改变某个出租车对象的某些变量(比如出租车的状态),使出租车做相应的运动,完成题目要求。
关于这三次作业中被发现的bug,在第五次作业中,我被发现的bug大致有没有严格遵循确定的时间,如电梯每上一层楼的时间严格为3.00s,解决办法为自己不采用System.currentTimeMillis()取得时间,而是自己采用假的时间,每次sleep的时间为小于3s的某个数。此外还有的bug是对于某些请求没有捎带,但是实际上是有处理捎带方面的代码的,判断最终某些请求没有捎带可能是时间处理与判断的问题。在第六次作业中,我犯的一个比较严重的问题就是移动文件或者改变文件名之后的recover操作反复触发了,初步分析应该是移动文件后的线程先后顺序不合理导致是否判断recover触发的操作比修改文件状态的操作执行地要早,即在判断是否recover的时候程序还认为该文件是未修改的状态。在第七次作业中,被发现的bug主要有时间为按照严格的200ms来执行,解决方法同第五次作业。此外还有一个选择的问题,即程序需要从抢了某个请求的多辆出租车中依据他们的状态来选择出最佳的出租车,然而出租车的状态是时刻变化的(每200ms随机运动一次)所以说执行判断相关的代码时出租车状态也在发生变化,从而导致错误的发生。还有一个错误是输出不严谨,输出应该单独开一个线程类来执行。
在分析别人bug所采用的策略上,我先从自己在写代码过程中觉得容易出错的地方触发,把测试自己程序bug的一些测试样例用来测试别人的程序,自己编程的时候觉得易错的地方就试一试。然后就是根据错误分支树来进行点对点的测试,对于每个分支树上的错误都构造几个测试样例来进行测试。关于线程相关的问题,我主要使用了一些时间上的策略,比如在不同的特定时间点输入请求,通过掐秒表的方法来观察程序对不同请求的同时执行处理是否到位,即判断线程之间是否同步。
关于这三次作业的心得体会,这三次作业让我对多线程编程从一无所知到较为熟练。首先,多线程之间应该尽量少使用共享的变量,尽可能避免出现读写的问题;如果必须要使用一些共享变量的话(如RequestQueue),必须对该变量的类进行线程保护,即对该类的读方法和写方法进行同步措施(比如加锁)。此外,还有一点务必要注意的地方就是变量的传输究竟是值传输还是地址传输的问题,一般来说,自己写的类以及系统的较为复杂的类和数组,在变量传输的时候传递的是地址,这个被传输的变量相当于就是在传输方和被传输方之间的共享变量,使用时务必要小心。对于String等基本变量,在传输的时候采用的是值传递,所以就不用担心共享变量的问题。关于设计原则,首先线程保护的类以及共享变量应该尽量的少,以免出现不必要的同步错误。此外对于某些需要存储的一些变量应该构造类的实例化变量来存储而不应该图省事而直接使用数组来存储。还有在设计的时候应对可能出现死锁的情况进行分析,防止死锁的发生。另外在设计的时候每个线程的run函数里每次循环执行的代码量应该不要差距太大,以免出现不可预料的情况。
标签:全局 同步 moni hand lock input 总结 相关 状态
原文地址:https://www.cnblogs.com/lvba/p/8978204.html