我正在寻找改进一个我相信当它的输入在多个工作线程之间共享时不是线程安全的包.根据TDD原则,我应该首先编写一些失败的测试,这些测试在评估问题时肯定会有用.
我意识到这并不是一件简单的事情,而且天真的多线程测试将是不确定的,因为操作系统将确定调度以及各种操作交错的确切顺序.我过去看过并使用过MultithreadedTC,这很有用.但是,在这种情况下,我事先知道现有实现的确切位置,从而能够完成一系列覆盖它的测试.
但是,如果你还没有确切知道问题是什么,那么是否有一种很好的方法来编写一个很有可能抛出任何潜在问题的测试?是否有其他人发现有用的图书馆?我是否正确地认为,从纯粹的角度来看,多线程测试用例应该与通常的单线程测试一样是相同的调用和断言,只适用于多个工作线程?
我们欢迎任何有关工具/最佳实践/理念的提议.
Java Concurrency in Practice提供了一些有关如何编写并发问题测试的重要信息.然而,它们不是真正的单元测试.为并发问题编写真正的单元测试几乎是不可能的.
基本上它归结为这个.创建一堆测试线程并启动它们.每个线程都应该
等待倒计时闩锁
反复调用一些修改有问题的可变状态的方法
倒数第二个闩锁并退出
junit线程创建所有线程并启动它们,然后在第一个锁存器上倒计时一次让它们全部运行,然后等待第二个锁存器,然后对可变状态做出一些断言.
甚至比其他类型的错误更重要的是,在找到错误之后,更容易为并发错误编写失败的单元测试.
通过测试并发问题忘记获得好的结果.尝试减少同步并使问题变小.然后使用尽可能高的库支持来进行同步.并且只有你真的有尝试自己处理并发.当你知道每个工作人员的工作正确并且你所有的想法告诉你你有并发问题时,就会产生一些有趣的负载.Unittest框架及其扩展可以完成这项工作,但知道你不再测试任何单元了.(记住,你已经覆盖了那部分)
如果您的并发模型变得复杂,请检查适合SPIN的工具.
您必须解决两个基本问题.首先是:你如何生成线程冲突?其次,您如何验证您的测试?
第一个是直截了当的.用一把大锤子.编写一些能够检测一个线程踩到另一个线程的情况的代码,并在50个连续线程上运行该代码50次左右.您可以使用倒计时锁存器执行此操作:
public void testForThreadClash() { final CountDownLatch latch = new CountDownLatch(1); for (int i=0; i<50; ++i) { Runnable runner = new Runnable() { public void run() { try { latch.await(); testMethod(); } catch (InterruptedException ie) { } } } new Thread(runner, "TestThread"+i).start(); } // all threads are waiting on the latch. latch.countDown(); // release the latch // all threads are now running concurrently. }
你的testMethod()必须生成一种情况,在没有synchronized块的情况下,某个线程会踩到另一个线程的数据.它应该能够检测到这种情况并抛出异常.(上面的代码不会这样做.异常很可能是在其中一个测试线程上生成的,你需要在主线程上放入一个检测它的机制,但这是一个单独的问题.我把它留下了为简单起见,但你可以使用第二个锁存器执行此操作,测试可以在异常时提前清除.)
第二个问题比较棘手,但有一个简单的解决方案.问题是:你怎么知道你的锤子会产生冲突?当然,您的代码已同步块以防止冲突,因此您无法回答问题.但是你可以.只需删除已同步的关键字.由于它是使您的类线程安全的synchronized关键字,您可以删除它们并重新运行测试.如果测试有效,它现在将失败.
当我第一次编写上面的代码时,结果证明它是无效的.我从未见过冲突.所以它还不是一个有效的测试.但现在我们知道如何得到第二个问题的明确答案.我们得到了错误的答案,但我们现在可以修改测试以产生我们正在寻找的失败.这就是我所做的:我只是连续跑了100次.
for (int j=0; j<100; ++j) { testForThreadClash(); }
现在我的测试在大约第20次迭代时可靠地失败了.这证实了我的测试是有效的.我现在可以恢复synchronized关键字并重新运行测试,相信它会告诉我我的类是否是线程安全的.