Java中的多线程状态可见性:有没有办法将JVM变成最坏的情况?

假设我们的代码有 2 个线程(A 和 B)在某个地方引用了此类的同一实例:

public class MyValueHolder {

    private int value = 1;

    // ... getter and setter

}

当线程 A 这样做时,不能保证线程 B 会读取该值:理论上可以永远返回。myValueHolder.setValue(7)myValueHolder.getValue()1

然而,在实践中,硬件迟早会清除第二级缓存,因此线程B迟早会读取(通常更早)。7

有没有办法让JVM模拟最坏的情况,在这种情况下,它一直为线程B永远返回1在这种情况下,这对于使用现有测试测试我们的多线程代码非常有用。


答案 1

jcstress维护者在这里。有多种方法可以回答这个问题。

  1. 最简单的解决方案是将 getter 包装在循环中,并让 JIT 提升它。这允许用于非易失性字段读取,并通过编译器优化模拟可见性失败。
  2. 更复杂的技巧包括获取OpenJDK的调试版本,并使用,有效地执行指令调度模糊测试。有问题的负载可能会漂浮在您可以通过产品进行常规测试检测到的地方。-XX:+StressLCM -XX:+StressGCM
  3. 我不确定是否有实际的硬件将写入值保持足够长的不透明以缓存一致性,但是使用jcstress构建测试用例有点容易。您必须记住,(1)中的优化也可能发生,因此我们需要采用一种技巧来防止这种情况。我认为这样的事情应该有效。

答案 2

如果有一个Java编译器,它会故意执行尽可能多的奇怪(但允许的)转换,以便能够更容易地破坏线程不安全的代码,就像Csmith for C.不幸的是,这样的编译器并不存在(据我所知)。

同时,您可以尝试使用jcstress库*,并在多个架构上练习代码,如果可能的话,使用较弱的内存模型(即不是x86)来尝试破坏代码:

Java 并发压力测试 (jcstress) 是一种实验性工具,一套测试有助于研究 JVM、类库和硬件中并发支持的正确性。

但最终,不幸的是,证明一段代码是100%正确的唯一方法是代码检查(我不知道有一个静态代码分析工具能够检测所有竞争条件)。

*我没有使用它,我不清楚哪个jcstress和java-concurrency-torture库是最新的(我怀疑jcstress)。


推荐