JCStress - 奇怪的 volatile 和 static 关键字行为

JCStress - strange volatile and static keyword behavior

我有这个简单的 Jcstress 测试:

package io.denery;


import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.IIII_Result;

@JCStressTest
@Outcome(expect = Expect.ACCEPTABLE_INTERESTING)
@State
public class RaceIRIWTest {
    volatile int x, y;

    @Actor
    public void actor1() {
            x = 1;
    }

    @Actor
    public void actor2() {
            y = 1;
    }

    @Actor
    public void actor3(IIII_Result r) {
            r.r1 = x;
            r.r2 = y;
    }

    @Actor
    public void actor4(IIII_Result r) {
            r.r3 = y;
            r.r4 = x;
    }
}

但是这次测试的结果是:

[OK] io.denery.RaceIRIWTest (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+WhiteBoxAPI, -XX:-RestrictContended, -Dfile.encoding=UTF-8, -Duser.country=RU, -Duser.language=en, -Duser.variant, -XX:-TieredCompilation, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN]) Observed state Occurrences Expectation Interpretation
0, 0, 0, 0 22,180,923 ACCEPTABLE_INTERESTING
0, 0, 0, 1 721,581 ACCEPTABLE_INTERESTING
0, 0, 1, 0 13,347 ACCEPTABLE_INTERESTING
0, 0, 1, 1 456,971 ACCEPTABLE_INTERESTING
0, 1, 0, 0 344,068 ACCEPTABLE_INTERESTING
0, 1, 0, 1 36 ACCEPTABLE_INTERESTING
0, 1, 1, 0 528,641 ACCEPTABLE_INTERESTING
0, 1, 1, 1 258,265 ACCEPTABLE_INTERESTING
1, 0, 0, 0 204,088 ACCEPTABLE_INTERESTING
1, 0, 0, 1 667,580 ACCEPTABLE_INTERESTING
1, 0, 1, 1 94,877 ACCEPTABLE_INTERESTING
1, 1, 0, 0 663,159 ACCEPTABLE_INTERESTING
1, 1, 0, 1 306,251 ACCEPTABLE_INTERESTING
1, 1, 1, 0 128,608 ACCEPTABLE_INTERESTING
1, 1, 1, 1 18,838,186 ACCEPTABLE_INTERESTING

我们看到竞争条件,但如果我将 static 放入 volatile int x, y 并删除 volatile 关键字,那么 jcstress 测试的结果将是:

[OK] io.denery.RaceIRIWTest (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+WhiteBoxAPI, -XX:-RestrictContended, -Dfile.encoding=UTF-8, -Duser.country=RU, -Duser.language=en, -Duser.variant, -XX:-TieredCompilation, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN]) Observed state Occurrences Expectation Interpretation
1, 1, 1, 1 100,299,061 ACCEPTABLE_INTERESTING

为什么 volatile 不能修复竞态条件,而 static 关键字可以修复它?还是 Jcstress 问题?

我不是 JCStress 专家。

但是您缺少的是您需要指定法律结果,请参见下面的 link 示例。

https://github.com/openjdk/jcstress/blob/master/jcstress-samples/src/main/java/org/openjdk/jcstress/samples/concurrency/mutex/Mutex_02_DekkerAlgorithm.java

[禁忌]

在 IRIW 测试的情况下,您想要防止不同的 CPU 对不同地址的存储,可以看出顺序不正确。所以你想防止 actor 3 看到 r1=1, r2=0(所以在 y 之前看到 x)。演员 4 看到 r3=1, r4=0(因此在 x 之前看到 y)。所以禁止的情况是 1,0,1,0

[易变]

我用volatile检查结果时,并没有遇到禁止的情况。原因是带有 volatile 的示例没有数据竞争,因此它只产生顺序一致 (SC) 执行。对于 SC,总有一些解释执行的 loads/stores 的总顺序。因为有一个total order,所以它也会把store order到不同CPU发出的不同地址。由于 SC 执行需要与程序顺序 (PO) 一致,因此 SC 将阻止重新排序负载。

[没有易变的静态]

带有静态的示例包含数据竞争,因为在读取和写入之间的边缘之前没有发生。所以只有静态允许看到禁止执行1,0,1,0

一个简单的解释是 2 个加载被 JIT 重新排序,或者 CPU 如果它允许乱序加载(例如 ARM)。我的猜测是您只能看到 1,1,1,1 因为 xy 没有被 JCStress 取消设置;但是我对JCStress还不够了解。

[多余]

为了使示例更有趣,我将使 2 个存储不透明存储和加载不透明加载,并在 2 个加载之间放置一个 [LoadLoad] 栅栏以确保它们按顺序执行。 Opaque 将确保 load/store 不会被优化掉,它会提供原子性(基本保证),但不会为不同地址的 loads/stores 提供任何排序​​保证。因此,您将测试 CPU.

的内存一致性保证

IRIW 不应在现代 CPUs 上失败,因为它们是多副本原子的,我所知道的唯一 CPU 可能表现出这种行为的是 PowerPC。