在 Java 中单元测试线程安全的任何令人满意的方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/424516/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Any satisfactory approaches to unit testing thread safety in Java?
提问by Andrzej Doyle
I am looking at improving a package that I believe not to be threadsafe when its input is shared between multiple worker threads. According to TDD principles, I should write some tests that fail in the first instance, and these would certainly be useful in assessing the problem.
我正在考虑改进一个包,当它的输入在多个工作线程之间共享时,我认为它不是线程安全的。根据 TDD 原则,我应该编写一些一开始就失败的测试,这些测试肯定对评估问题有用。
I realise that this is not a simple thing to acheive, and that naively, multi-threaded tests will be nondeterministic as the operating system will determine scheduling and the exact order that various operations are interleaved. I have looked at and used MultithreadedTCin the past, and this was useful. However, in that case I knew in advance exactly where the existing implementation fell down, and thus was able to cook up a nice set of tests that covered it.
我意识到这不是一件容易实现的事情,而且天真的多线程测试将是不确定的,因为操作系统将确定调度以及各种操作交错的确切顺序。我过去看过并使用过MultithreadedTC,这很有用。但是,在那种情况下,我事先确切地知道现有实现失败的地方,因此能够编写一组很好的测试来覆盖它。
However, if you're not at the point where you know exactly what the problem is, is there a good way of going about writing a test that stands a good chance of throwing up any potential problems? Are there any libraries that others have found helpful? Would I be right in thinking that from a purist point of view, a multi-threaded test case should just be the same calls and assertions as the usual single-threaded test, only run with multiple worker threads as appropriate?
但是,如果您还不能确切地知道问题出在哪里,那么是否有一种很好的方法来编写一个很可能会抛出任何潜在问题的测试?是否有其他人认为有帮助的图书馆?我认为从纯粹主义者的角度来看,多线程测试用例应该与通常的单线程测试具有相同的调用和断言,仅在适当的情况下与多个工作线程一起运行吗?
Any offers on tools/best practices/philosophy in general would be welcome.
欢迎提供有关工具/最佳实践/哲学的任何报价。
采纳答案by Rene
Forget getting good results by testing for concurrency problems. Attempt to reduce synchronization and make the problem smaller. Then use as high as possible library support to do synchronization. And only if you really have then try to handle the concurrency yourself. When you know each worker does its work correct and all your thinking tell you that you have the concurrency problem licked then generate some interesting load. Unittest frameworks and their extensions can do that job, but know that you are not testing any units anymore. (Remember, you already had that part covered)
忘记通过测试并发问题来获得好的结果。尝试减少同步并使问题更小。然后使用尽可能高的库支持来做同步。并且只有当你真的有然后尝试自己处理并发。当您知道每个 worker 的工作都是正确的,并且您的所有想法都告诉您并发问题已解决时,就会产生一些有趣的负载。Unittest 框架及其扩展可以完成这项工作,但要知道您不再测试任何单元。(请记住,您已经涵盖了该部分)
If your concurrency model gets complicated check out tools suited for that like SPIN.
如果您的并发模型变得复杂,请查看适合该模型的工具,例如SPIN。
回答by Craig P. Motlin
Java Concurrency in Practicehas some great information about how to write tests for concurrency issues. However they are not true unit tests. It is nearly impossible to write a true unit test for a concurrency issue.
Java Concurrency in Practice 中有一些关于如何为并发问题编写测试的重要信息。然而,它们不是真正的单元测试。几乎不可能为并发问题编写真正的单元测试。
Basically it boils down to this. Create a bunch of test threads and start them. Each thread should
基本上它归结为这一点。创建一堆测试线程并启动它们。每个线程应该
- wait for a count down latch
- repeatedly call some method that modifies the mutable state in question
- count down on a second latch and exit
- 等待倒计时锁存器
- 重复调用一些修改相关可变状态的方法
- 倒计时第二个闩锁并退出
The junit thread creates all the threads and starts them, then counts down on the first latch once to let them all go, then waits for the second latch, then makes some assertions about the mutable state.
junit 线程创建所有线程并启动它们,然后在第一个锁存器上倒计时一次,让它们全部离开,然后等待第二个锁存器,然后对可变状态进行一些断言。
Even more so than other types of bugs, it's easier to write a failing unit test for a concurrency bug afteryou have found the bug.
与其他类型的错误相比,更容易在发现错误后为并发错误编写失败的单元测试。
回答by MiguelMunoz
There are two basic problems you have to solve. The first is this: how do you generate a thread clash? Second, how do you validate your test?
您必须解决两个基本问题。第一个是:如何产生螺纹冲突?其次,你如何验证你的测试?
The first one is straightforward. Use a big hammer. Write some code that's capable of detecting the case where one thread steps on another, and run that code 50 times or so on 50 consecutive threads. You can do this with a countdown latch:
第一个很简单。使用大锤子。编写一些能够检测一个线程踩到另一个线程的情况的代码,并在 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.
}
Your testMethod() has to generate a situation where, without the synchronized block, some thread will step on another thread's data. It should be able to detect this case and throw an exception. (The code above doesn't do this. The exception will most likely be generated on one of the test threads, and you need to put in a mechanism that will detect it on the main thread, but that's a separate issue. I left it out for simplicity, but you can do this with a second latch, which the test can prematurely clear on an exception.)
您的 testMethod() 必须产生这样一种情况,即在没有同步块的情况下,某个线程将踩踏另一个线程的数据。它应该能够检测到这种情况并抛出异常。(上面的代码没有这样做。异常很可能会在一个测试线程上生成,您需要放入一种机制来在主线程上检测它,但这是一个单独的问题。我离开了为简单起见,但您可以使用第二个闩锁来执行此操作,测试可以过早地清除异常。)
The second question is trickier, but there's a simple solution. The question is this: How do you know that your hammer will generate a clash? Of course, your code has synchronized blocks to prevent a clash, so you can't answer the question. But you can. Just remove the synchronized keywords.Since it's the synchronized keyword that makes your class threadsafe, you can just remove them and re-run the test. If the test is valid, it will now fail.
第二个问题比较棘手,但有一个简单的解决方案。问题是:你怎么知道你的锤子会产生碰撞?当然,您的代码具有同步块以防止冲突,因此您无法回答这个问题。但是你可以。只需删除同步的关键字。由于是同步关键字使您的类成为线程安全的,因此您可以删除它们并重新运行测试。如果测试有效,它现在将失败。
When I first wrote the code above, it turned out to be invalid. I never saw a clash. So it's not (yet) a valid test. But now we know how to get a clear answer to the second question. We're getting the wrong answer, but we can now tinker with the test to generate the failure we're looking for. Here's what I did: I just ran the test 100 times in a row.
当我第一次写上面的代码时,结果是无效的。我从未见过冲突。所以它(还)不是一个有效的测试。但现在我们知道如何得到第二个问题的明确答案。我们得到了错误的答案,但我们现在可以修改测试以生成我们正在寻找的失败。这就是我所做的:我只是连续运行了 100 次测试。
for (int j=0; j<100; ++j) {
testForThreadClash();
}
Now my test failed reliably on about the 20th iteration or so. This confirms my test is valid. I can now restore the synchronized keyword and rerun the test, confident that it will tell me if my class is threadsafe.
现在我的测试在大约第 20 次迭代时可靠地失败了。这证实了我的测试是有效的。我现在可以恢复 synchronized 关键字并重新运行测试,确信它会告诉我我的类是否是线程安全的。
回答by Phil
In some cases, I've found I can force a particular problematic interleaving of calls to a potentially un-thread-safe class by using multiple threads that synchronize with each other, perhaps with the use of CountDownLatch or some such concurrency mechanism. Sometimes this just doesn't work however, for instance if you're trying to test what happens if two threads are in the same method at the same time.
在某些情况下,我发现我可以通过使用彼此同步的多个线程,可能是使用 CountDownLatch 或某些此类并发机制来强制对潜在的非线程安全类的调用进行特定的有问题的交错调用。然而,有时这只是行不通,例如,如果您试图测试如果两个线程同时处于同一方法中会发生什么。
Here's an interesting article (don't know much about the tool, though) : http://today.java.net/pub/a/today/2003/08/06/multithreadedTests.html
这是一篇有趣的文章(虽然对该工具了解不多):http: //today.java.net/pub/a/today/2003/08/06/multithreadedTests.html
回答by Jonathan
There is nothing wrong with unit testing multi-threaded code if threading is the point of the code you're testing (think concurrent data structures). The general approach is to block the main test thread, perform assertions in separate threads, capturing and re-throwing any failed assertions in the main thread, else unblock the main test thread so the test can complete. It's pretty straightforward to do with ConcurrentUnit:
如果线程是您正在测试的代码的重点(想想并发数据结构),那么单元测试多线程代码并没有错。一般的方法是阻塞主测试线程,在单独的线程中执行断言,在主线程中捕获并重新抛出任何失败的断言,否则解除主测试线程的阻塞以便测试可以完成。使用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);
}
The key here is that any failed assertions in any thread will be re-thrown in the main thread by the waiter, allowing the test to pass or fail as it should.
这里的关键是任何线程中任何失败的断言都将在主线程中由 重新抛出waiter,从而允许测试按预期通过或失败。

