当前,当我们测试多线程Java时,我们通过尽可能多的线程来调用被测类。而且由于测试不是确定性的,因此我们会尽可能重复进行此测试。
这种方法的缺点是,大多数时候我们的错误测试都成功了,这使得调试多线程错误成为一场噩梦。因此,我开发了一个开源工具vmlens来确定多线程Java的JUnit测试。并使调试更容易。
这个想法是为给定的测试执行所有可能的线程交织。并报告失败的线程交织,这使调试成为可能。
A Test for a Concurrent Counter 以下示例显示如何使用vmlens为并发计数器编写测试。所有测试都在package的GitHub项目vmlens-examples中。 com.vmlens.examples.tutorial.counter
com.vmlens.examples.tutorial.counter
import com.vmlens.api.AllInterleavings; public class TestCounterNonVolatile { int i = 0; @Test public void test() throws InterruptedException { try (AllInterleavings allInterleavings = new AllInterleavings ("tutorial.counter.TestCounterNonVolatile");) { while (allInterleavings.hasNext()) { i = 0; Thread first = new Thread(() -> { i++; }); Thread second = new Thread(() -> { i++; }); first.start(); second.start(); first.join(); second.join(); assertEquals(2,i); } } } }
我们i从两个线程增加字段。并且在两个线程都完成之后,我们检查计数是否为2。诀窍是,通过使用class遍历所有线程交织的while循环来包围完整的测试AllInterleavings。
AllInterleavings
vmlens作为Java代理运行,并使用字节码转换来计算所有线程交织。因此,您需要按如下所述在Maven Pom中配置vmlens 。运行测试后,我们可以在target / interleave / elements.html文件中的interleave报告中查看所有测试运行的结果。
我们的测试(名称为tutorial.counter.TestCounterVolatile的测试5)由于数据争用而失败。数据竞争意味着对共享字段的读取和写入未正确同步。JIT编译器或CPU可以对不正确同步的读写进行重新排序。这里可以 很重要。通常,错误同步的读写会返回正确的结果。仅在非常特殊的情况下,通常将特定的CPU体系结构,特定的JVM和特定的线程交织在一起才能导致错误的值。
vmlens检查每个字段访问是否正确同步以检测数据竞争。
并发挥发性计数器的测试 为了解决数据争用问题,我们将字段声明为易失性:
public class TestCounterVolatile { volatile int i = 0; @Test public void test() throws InterruptedException { try (AllInterleavings allInterleavings = new AllInterleavings ("tutorial.counter.TestCounterVolatile");) { while (allInterleavings.hasNext()) { i = 0; Thread first = new Thread(() -> { i++; }); Thread second = new Thread(() -> { i++; }); first.start(); second.start(); first.join(); second.join(); assertEquals(2,i); } } } }
这样可以解决数据争用,但是现在断言失败了:
TestCounterVolatile.test:22 expected:<2> but was:<1>
要查看出了什么问题,请在交错报告中单击测试tutorial.counter.TestCounterVolatile。这向我们展示了错误的交错:
错误是两个线程都先读取变量i,然后再更新变量。因此,第二个线程将覆盖第一个线程的值。
A Test With an Atomic Counter 要编写正确的并发计数器,我们使用类AtomicInteger:
public class TestCounterAtomic { AtomicInteger i = new AtomicInteger(); @Test public void test() throws InterruptedException { try (AllInterleavings allInterleavings = new AllInterleavings ("tutorial.counter.TestCounterAtomic");) { while (allInterleavings.hasNext()) { i.set(0); Thread first = new Thread(() -> { i.incrementAndGet(); }); Thread second = new Thread(() -> { i.incrementAndGet(); }); first.start(); second.start(); first.join(); second.join(); assertEquals(2,i.get()); } } } }
现在我们的计数器的增量是原子的,我们的测试终于成功了。
结论
如我们所见,执行多线程测试的所有线程交织使多线程测试具有确定性。而且它使调试失败的测试成为可能。为了测试所有线程交织,我们使用一个while循环对测试进行了包围,该循环使用class遍历了所有线程交织AllInterleavings。vmlens使用字节码转换来计算所有线程交织。因此,您还需要按如下所述在Maven Pom中配置vmlens。如果测试失败,您可以查看发生故障的线程交错调试我们的测试。
原文链接:http://codingdict.com