oo第二单元总结
在这一单元的学习中,我初次接触到了多线程程序的设计。让我感受到,一些任务是单线程程序很难完成的;而多线程程序,在线程并行的优势下,却也会引入更多的问题。就此展开了一系列练习的作业,从单电梯调度,可捎带调度,到最后多电梯调度的作业中,我也逐渐对线程间通信、同步与互斥的设计越来越清晰了。先分析一下这几次的作业吧。
一、作业的设计策略
1、第一次作业,设计了2个线程:
Elevator线程模拟电梯执行任务,Monitor线程负责监视并接受请求;
Request类的实例作为共享对象,存储了从Monitor接受到的请求队列,并给Elevator提供请求。两个线程在访问共享对象存请求、取请求时,进行同步控制。
2、第二次作业与第一次基本相同,多出Adjuster类,控制Elevator的具体调度。
3、第三次作业,实例化了3个电梯线程,为每个电梯实例化了1个request类,存该电梯应执行的请求队列;新增Control类进行电梯任务的平衡负载,将Monitor对象接受的请求及换乘后构造的新请求,分配到3个请求队列中,同时,判断所有任务是否都已执行完毕,控制所有线程结束。在control对象上新加同步控制,对于分配、结束状态改变、判定的方法进行同步控制。
二、基于度量分析程序
三次作业中Main类,都是作为启动用,构造好共享对象及线程,start线程后,就结束。
1、第一次作业
request类实例作为共享对象,monitor存,elevator取。
度量:复杂度不高,结构清晰;
2、第二次作业
新增的Adjuster类,类似于中介,把要执行的请求从request中取出,介绍给elevator
度量:复杂度不高,结构清晰。
3、第三次作业
新增control类进行全局控制,平衡分配任务,控制所有进程结束,及包括静态方法,控制电梯是否可停靠。
度量:复杂度不高,结构清晰。
三、分析自己的bug
1、第一次作业,未被发现bug
2、第二次作业,有严重bug,进行顺向捎带后,分配的逆向主任务会被忽略。
位置:Adjuster类中,findin方法。
原因:少考虑了一种情况,应加MainRequest!=null时,target = MainRequest.getFromFloor()
与多线程设计无关,实在是自己脑抽了没考虑全面,自己测试的时候,随便试了几下,觉得自己没bug,太不仔细了。
3、第三次作业,互测bug,电梯线程可能会提前结束,使换乘乘客请求无法送达。
位置:Control类中,run方法
原因:未判断三部电梯请求队列是否为空,只是三部电梯都无目标时就结束。
一般情况下,三部电梯都无目标时,请求队列也会为空,考虑不全面。
四、发现bug策略
1、自己分类设计几类可能出错的情况,小规模数据手动测试。
2、浏览代码,对关键代码进行分析(比如同步控制语句),试图找到bug。
3、利用随机数据的评测机,进行重复、大量的测试。
与第一单元测试相比,多线程较难发现的bug主要是集中在线程安全问题上,而这些问题,在不仔细阅读分析代码前,是比较难进行重复稳定的复现的。使用评测程序,在重复、大量的测试集下,偶然可能会发现bug,但多数时间还是做着低效的重复工作。
五、心得体会
多线程程序引入的线程安全问题,主要在于多个线程对于共享对象的读写、写写冲突。对于此类问题,可以采用上锁的方式,进行访问控制。例如,对一些方法或对象,修饰synchronized关键字,就可以简易的实现同时只有一个进程进入所修饰的代码块中;或是使用Lock实现读写锁等更灵活的锁操作;还有一些提供的线程安全类,比如AtomicInteger,BlockingQueue……,如此,对于x++,可以直接使用原子操作x.GetandIncrement,来实现线程的安全。
在设计多线程程序时,应尽量减少线程对象间的直接交互;若是需要修改线程对象的某些数据,则应当提供一些public方法进行调用,而不是将这些数据直接交给其他线程修改。分析清楚需要进行同步的代码区,只在必要的同步代码块进行synchronized等同步语句的控制,来避免不必要的性能上的浪费。