在Java中使用unit testing线程安全的令人满意的方法?

我正在寻找改善一个包,我相信当它的input是多个工作线程之间共享时,不要线程安全。 根据TDD原则,我应该写一些首先失败的testing,这对于评估问题肯定是有用的。

我意识到这不是一件简单的事情,并且天真地说,multithreadingtesting将是不确定的,因为操作系统将确定调度和各种操作交错的确切顺序。 我曾经看过并使用过MultithreadedTC ,这很有用。 但是,在这种情况下,我事先知道现有的实施scheme在哪里倒下了,从而能够做出一套很好的testing。

但是,如果你不是在确切地知道问题所在,那么是否有一个很好的方法来编写一个testing,这个testing很有可能会抛出任何潜在的问题? 有没有其他人find有用的图书馆? 我认为从纯粹的angular度来看,我认为multithreadingtesting用例应该和通常的单线程testing相同的调用和断言,只适用于多个工作线程?

任何关于工具/最佳实践/哲学的提议都是受欢迎的。

忘记通过testing并发性问题来获得好的结果。 尝试减less同步并使问题更小。 然后尽可能使用库支持来做同步。 只有当你真的尝试自己处理并发。 当你知道每个工人的工作是正确的,你所有的想法都告诉你,你已经舔了并发问题,然后产生一些有趣的负载。 unit testing框架及其扩展可以完成这个工作,但是知道你不再testing任何单元。 (记住,你已经有了这个部分)

如果您的并发模型变得复杂,请检查适合类似于SPIN的工具。

实践中的Java并发有关于如何编写testing并发问题的一些很好的信息。 但是他们不是真正的unit testing。 为并发问题编写真正的unit testing几乎是不可能的。

基本上归结到这一点。 创build一堆testing线程并启动它们。 每个线程应该

  • 等待倒数锁存器
  • 反复调用一些修改所讨论的可变状态的方法
  • 倒数第二个闩锁并退出

junit线程创build所有线程并启动它们,然后在第一个锁存器上倒计数一次,让它们全部去掉,然后等待第二个锁存器,然后对可变状态做出一些断言。

甚至比其他types的错误更容易, find错误之后 ,为并发错误编写一个失败的unit testing更容易。

有两个基本问题需要解决。 首先是这样的:你怎么产生一个线程冲突? 其次,你如何validation你的testing?

第一个很简单。 使用一个大锤子。 编写一些能够检测到一个线程跨越另一个线程的代码,并在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块的情况下,某个线程将会跳到另一个线程的数据上。 它应该能够检测到这种情况并抛出exception。 (上面的代码不会这样做,这个exception很可能在其中一个testing线程上产生,而且你需要在主线程中join一个检测机制,但这是一个单独的问题。为了简单起见,但是你可以用第二个锁存器来做到这一点,在exception情况下,testing可以提前清除。)

第二个问题更棘手,但有一个简单的解决scheme。 问题是:你怎么知道你的锤子会产生冲突? 当然,你的代码有同步块来防止冲突,所以你不能回答这个问题。 但是你可以。 只要删除同步的关键字。 由于它是使您的类线程安全的同步关键字,您可以删除它们并重新运行testing。 如果testing是有效的,现在将失败。

当我第一次写上面的代码时,结果是无效的。 我从来没有看到冲突。 所以这还不是一个有效的testing。 但现在我们知道如何对第二个问题作出明确的答复。 我们得到了错误的答案,但现在我们可以修补testing来产生我们正在寻找的失败。 这就是我所做的:我连续testing了100次。

 for (int j=0; j<100; ++j) { testForThreadClash(); } 

现在我的testing在大约20次左右的时候失败了。 这证实了我的testing是有效的。 我现在可以恢复sync​​hronized关键字并重新运行testing,相信它会告诉我,如果我的课是线程安全的。

在某些情况下,我发现我可以通过使用多个相互同步的线程(可能使用CountDownLatch或一些这样的并发机制)来强制将一个特定的有问题的交叉调用交给一个潜在的非线程安全类。 有时候,这只是不起作用,例如,如果你想testing如果两个线程同时在同一个方法中会发生什么。

这里有一篇有趣的文章(虽然不太了解这个工具): http : //today.java.net/pub/a/today/2003/08/06/multithreadedTests.html

如果线程是您正在testing的代码(考虑并发数据结构)的话,unit testingmultithreading代码没有任何问题。 一般的方法是阻塞主testing线程,在单独的线程中执行断言,在主线程中捕获并重新抛出任何失败的断言,否则取消阻塞主testing线程,以便testing可以完成。 使用ConcurrentUnit非常简单:

 @Test public void shouldDeliverMessage() throws Throwable { final Waiter waiter = new Waiter(); messageBus.registerHandler(message -> { // Called on separate thread waiter.assertEquals(message, "foo"); // Unblocks the waiter.await call waiter.resume(); }; messageBus.send("foo"); // Wait for resume() to be called waiter.await(1000); } 

这里的关键是任何线程中的任何失败的断言将被服务器重新抛出在主线程中,从而允许testing通过或失败。