OO第二次总结 多线程从玄幻到入门
多线程从玄幻到入门
三次设计策略的变化
第一次,多线程电梯:其实基本还不理解多线程的同步控制,根本不太理解“锁”是什么,以及线程之间是怎么通信的。因此,在多线程电梯中,虽然我知道电梯、队列和请求生成器之间的交互模式,涉及到多线程的部分,多半是尝试出来的。
第二次,IFTTT:其实在这个时候已经感受到了直接设计线程安全类可能会简化程序,因为如果类的方法都是线程安全的,其实在其他类调用的时候就不太需要考虑线程安全的问题了,而这种方式在OO第四次实验当中得到了更好的体现。
量化分析
多线程电梯
IFTTT
出租车
从设计原则的角度,总结一下自己的代码质量问题,我觉得在Taxi作业中,我的责任均衡分配原则遵守的不太好,我的Car类比较God,又能行走如飞,又能找出最短路,我的Map类就比较Idiot,除了解析地图,基本上就是个数组。如果再给我一次重新做人的机会,我会把SPFA放在Map类中,用地图类提供寻找最短路的服务,比较合理。
自己的BUG
多线程电梯:
犯了一个致命错误,当请求时间晚于最短到达时间时,没有更新电梯最后关门的时间。last_close_time = Math.max(last_close_time, req.getT());
IFTTT:有一处线程安全没做好,导致文件会发生反复删除recover的情况。
出租车:除了忘记判断起点终点相同的情况外,没有什么bug。
如何发现别人的BUG
列出自己所采取的测试策略及有效性,并特别指出是否结合被测程序的代码设计结构来设计测试用例分析自己采用了什么策略来发现线程安全相关的问题
既然多线程主要是为了解决并发问题,那么最简单的就是使用高并发的测试数据进行测试了。在出租车这次作业中体现的尤为明显。算法的复杂度是一方面,我使用的SPFA算法实时计算最短路,在网格图中将会退化成O(N)的复杂度。事实上由于地图是固定的,也可以预处理每个点对之间的最短路,存储后直接调用,但是这样的问题就是存储的结构比较复杂,如果用数组存维数就比较多。
所谓的高并发,就是在控制台一次输入大量请求,我设计的数据就是均匀地将请求铺满整个地图,目的地都是地图的中心。这样,基本上所有的车都会被派单,而且都会朝着同一个地点(40,40)出发。
这也是我使用的唯一一个样例,但是这一个样例就测出了我的分配任务的bug(我测的同学写的真的非常好,我认为这是他唯一的bug)。
这个样例有如下几点好处:
1、测试了最高并发的情况,所有的车都会被派单,线程安全的问题自然很容易暴露了。2、测试了编程者使用的算法是否足够合理。倘若使用在线的最短路算法,就需要实时计算100次单源最短路,这对编程者使用的算法复杂度就有了一定的要求,因为毕竟我们的系统模拟汽车走一格的时间只有200ms,所以计算的时间太长就会引起误差。3、测试了编程者路径选择的正确性。事实上,这100辆车走向同一个点的路径一定不是100条,而是远远小于100条,因为最短的路径还是十分有限的,在这里,正常的现象应该是所有车开始出发,然后车的轨迹像水流一样,慢慢融合到了一起,所谓殊途同归,效果就是酱紫:
使用这个简单的样例,我测出了一个bug,bug出现的原因是这样的:
这张图我也想借此说明,如果测试者和被测试者都能以这样的态度交流,世界将变成美好的人间。这并不是温柔以待,这才叫交流学术。
心得体会
在第一次多线程电梯的时候,线程安全是最让人头疼的事,而且对于初学者而言,其实最重要的不是到底安不安全,而是连如何移交控制权都不能清楚地想清楚,因此,往往会出现有些模块根本没有得到控制权的情况,比如我,自己就尝试了很久。多线程基本只能使用System.out.println来调试,不过这并没有对我造成什么限制,因为本来我也只用这种调试方式23333。
(第一次调试多线程)
感觉最重要的还是理解生产者消费者模型,其实生产者消费者的数量并不重要,因为毕竟面向对象,开一个线程无非是new一个对象而已,因此对于每次任务,可以先测试一个生产者、一个消费者的情况,只要托盘是线程安全的就好了。这个经验在OO第四次实验中得到了完美的体现,使用阻塞队列(BlockingQueue)或者自己构造线程安全的请求队列类,对生产者和消费者而言就不用关心锁的问题了,会很好地简化编程的思维复杂度。