第12章 并发程序的测试
大致分为两类:安全性测试和活跃性测试
12.1 正确性测试
找出需要检查的不变性条件和后验条件。接下来将构建一组测试用例来测试一个有界缓存。程序清单12-1给出了BoundedBuffer的实现,其中使用Semaphore来实现缓存的有界属性和阻塞行为。
BoundedBuffer实现了一个固定长度的队列,其中定义了可阻塞的put和take方法,并通过两个计数信号量进行控制。(实际情况中如果需要一个有界缓存,应该直接使用ArrayBlockingQueue或者LinkedBlockingDeque,而不是自己编写)。
12.1.1 基本的单元测试
BoundedBuffer的最基本单元测试类似与在串行上下文中执行的测试。首先创建一个有界缓存,然后调用它的各个方法,并验证它的后验条件和不变性条件。我们很快会想到一些不变性条件:新建立的缓存应该是空的,而不是满的。另一个略显复杂的安全测试是,将N个元素插入到容量为N的缓存中,然后测试缓存是否已经填满。
12.1.2 对阻塞操作的测试
在测试并发的基本属性时,需要引入多个线程。大多数测试框架并不能很好地支持并发性测试。在java.util.concurrent的一致性测试中,一定要将各种故障与特定的测试明确地关联起来。因此JSR 166专家组创建了一个基类,其中定义了一些可以在tearDown期间(主要实现测试完成后的垃圾回收等工作)传递和报告失败信息,并遵循一个约定:每个测试必须等待它锁创建的全部线程结束以后才能完成。
如果某方法需要在某些特定条件下阻塞,那么当测试这种行为时,只有当线程不再继续执行时,测试才是成功的。要测试一个方法的阻塞行为,类似于测试一个抛出异常的方法:如果这个方法可以正常返回,那么就意味着测试失败。在测试方法的阻塞行为时,将引入额外的复杂性:当方法被成功阻塞后,还必须使用方法接触阻塞。实现这个功能的一种简单方式就是使用中断——在一个单独的线程中启动一个阻塞操作。来个例子:
看了后面忘了前面。。。 现在补下之前的一些知识:
(1) 中断是通过调用Thread.interrupt()方法来做的. 这个方法通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了. 对于非阻塞中的线程, 只是改变了中断状态, 即Thread.isInterrupted()将返回true; 对于可取消的阻塞状态中的线程, 比如等待在这些函数上的线程, Thread.sleep(), Object.wait(), Thread.join(), 这个线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为false.
(2) t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程
如果take操作由于某种意料之外的原因停滞了,那么支持时限的join方法能够确保测试最终完成。用于验证线程能否在一个条件等待上阻塞的Thread.getState方法并不可靠(详见p208)
12.1.3 安全性测试
上面的几个测试程序无法发现由于数据竞争而引发的错误。要测试在生产者——消费者模式中使用的类,一种有效的方法就是检查被放入队列中和从队列中取出的各个元素。可以通过一个对顺序敏感的校验和计算函数里计算所有入列元素和出列元素的校验和,并进行比较。如果二者相等那么测试就死成功的。如果要将这种方法扩展到多生产者-多消费者模式的情况,就需要对元素入列/出列顺序不敏感的校验和函数,从而在测试程序运行完后,可以将多个校验和以不同的顺序组合起来。(这部分看得各种懵比)
当线程到达栅栏位置时调用栅栏的await方法,这个方法阻塞直到所有线程都达到栅栏的位置。如果所有线程都达到了栅栏的位置,那么栅栏将被打开,所有线程被释放,栅栏将被重置以便下次使用。
12.1.4 资源管理的测试(p212)
12.1.5 使用回调(p213)
12.1.6 产生更多的交替操作(p214)
由于并发代码中的大多数错误都是一些低概率时间,因此在测试并发错误时需要反复地执行多次,所以有时候需要产生更多的交替操作。一种有用的方法是:在访问共享状态的操作中,使用Thread.yield将产生更多的上下文切换。
yield方法会使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
应该是面向切面的编程好伐,再次吐槽下这本书的翻译质量。
12.2 性能测试(p215)
12.2.1 在PutTakeTest中增加计时功能(p215)
记录整个运行过程的时间,然后除以操作数的量,从而得到每次操作的运行时间。使用一个栅栏动作来测量启动和结束时间:
老实说这部分我看到云里雾里的,不知道是不是翻译的原因(摔锅中...),二周目准备把这本书的英文版拿来看一遍。
12.2.2 多种算法的比较(p217)
12.2.3 响应性衡量(p219)
12.3 避免性能测试的陷阱(p220)
12.3.1 垃圾回收(p220)
垃圾回收的执行时序是无法预测的,因此在执行测试时,垃圾回收器可能在任何时刻运行。
12.3.2 动态编译(p220)
12.3.3 对代码路劲的不真实采样(p222)
12.3.4 不真实的竞争程度(p222)
12.3.5 无用代码的消除(p223)
12.4 其他的测试方法(p224)
总结:这章基本到后面就看不懂了...., 说了很多理论性的东西。其实我一直觉得理论这东西光说没用,要在实践的基础上理解才行。比较推崇先实践后理论的教学方式,而不是先从理论开始。这里再吐槽下这本书的翻译质量,坑啊。