http://www.cnblogs.com/shwen99/archive/2010/03/22/1691280.html
“并行程序易于产生 bug 不是什么秘密。编写这种程序是一种挑战,并且在编程过程中悄悄产生的 bug 不容易被发现。许多并行 bug 只有在系统测试、功能测试时才能被发现或由用户发现。到那时修复它们需要高昂的费用 -- 假设能够修复它们 -- 因为它们是如此难于调试。”以上论述来自IBM论坛中关于 ConTest 的一篇介绍文章,并且,我还要补充一点,这种 BUG 通常根本无法重现,以至于要找到发生 BUG 的原因都是非常的困难;即使幸运的找到了可能原因,修改了代码,要确认问题确实得到了解决,依然是非常的困难,因为根本无法进行有效的测试。
JUnit提供了很好的单元测试框架,但是对于多线程的并发测试,却无能为力。经过一番搜索,找到了一个叫 GroboUtils 的东西,关于它的介绍不少,这里不再重复,GroboUtils 解决了启动多线程以及当某个线程出错时将错误返回给 JUnit 的问题,现在我们可以比较简单启动多个线程并验证结果了。具体介绍请自己 google 一下,我这里简单的贴一个 Demo 代码,当然和官方以及一般的例子不同,我比较喜欢使用内部匿名类来启动线程。这个测试很简单,启动 5 个线程,并发的进行 i++ 运算,我有意的注释掉了同步代码,这样应该会导致最后测试无法通过。
public int i = 0 ;
}
public class ParalIncTest {
public static class Incrementor extends Thread {
public void run() {
}
}
@Test
public void testInc() throws Throwable {
for ( int i = 0 ; i < 1000 ; i ++ ) {
final ParalInc target = new ParalInc();
TestRunnable r = new TestRunnable() {
public void runTest() throws Throwable {
// synchronized(ParalIncTest.class) {
target.i ++ ;
// }
}
};
TestRunnable[] tcs = {r, r, r, r, r};
int threadCount = tcs.length;
MultiThreadedTestRunner mttr = new MultiThreadedTestRunner( tcs );
mttr.runTestRunnables( 2 * 60 * 1000 );
System.out.println( " final value: " + target.i);
if (target.i != threadCount) {
System.err.println( " Bug - at loop " + i);
}
assertEquals(threadCount, target.i);
}
}
}
开始的时候我并没有添加最外层循环,然而测试的结果很遗憾,怎么都无法让这段代码出错。加上循环以后,才偶尔会出错,也一般要循环到数百次以后才会发生错误。导致这种结果的原因,估计是线程的切换其实并发象我们想象的那么频繁,而且要恰恰在共享冲突点发生切换,这个概率也确实非常的小。如果能够控制虚拟机的线程切换,使得共享冲突概率更大一些那该是多么好啊。
因此,我找到了这篇文章 http://www.ibm.com/developerworks/cn/java/j-contest.html ,ConTest,这真是个好东西啊,配置好以后,还是上面那段测试代码,但是测试结果几乎都会检测到冲突,而且通常是在循环了2-3次后就检测到了。
ConTest 下载链接在从上面的文章中可直接找到,可以用 Eclipse plug-in 方式安装,update 地址 http://awwebx04.alphaworks.ibm.com/ettktechnologies/updates 。但是我实在没发现它给 Eclipse 添加了什么功能,其实我觉得主要还是只要下载到那个 ZIP 包就可以了,并不需要安装为 plug-in。
下载下来以后,ConTest 并不能自动的开始工作,需要一点点配置工作,这有点烦人。
首先要将 ConTest.jar 包拷贝到一个便于指定位置的地方,我一般倾向于将它放在工程目录的lib子目录下,另外 ZIP 包的 LIB 目录下还有一个 KingProperties 文件,要拷贝到和 ConTest.jar 相同的目录中。修改 KingProperties 文件,当然也可以不改,我主要是修改 output 目录,为 output = target,和 maven 保持一致,一切都输出到那里去。
这还只是一些准备活动,最后,要让 ConTest 开始工作,还需要修改 Run|RunConfigurations,在单元测试的 jvm 启动参数中指定启动 ConTest , 既添加 JVM 参数:
-javaagent:lib/ConTest.jar -Dcontest.targetClasses=demo/ParalIncTest
这里 -Dcontest.targetClasses=demo/ParalIncTest 既指定要进行并行性分析的类名,可以用逗号分隔指定多个类,当然这个参数本身也可以 KingProperties 文件中指定的,但是我觉得在命令行测试比较方便一些,毕竟从单元测试的角度来说,一次应该只测试一个类。在命令行测试,就不需要多套 KingProperties 配置文件。
至此,所有配置工作完成,依然像以前一样启动单元测试,ConTest 作为 javaagent 装载,因此它其实是在 ClassLoader 加载类字节码的时候介入,修改字节码在其中加入一些线程切换的代码,这样线程切换更频繁了,特别在一些同步块中,发生同步冲突的概率自然就大很多了。
总的来说,将 GroboUtils 和 ConTest 结合起来使用,效果还是相当不错的,并发测试变得简单多了,而且检测到冲突的可能性非常的大。