为什么同步顺序定义为所有同步操作的总顺序?

Why is synchronization order defined as total order over all of the synchronization actions?

在研究Java内存模型时,我对同步顺序(SO)的定义感到困惑。据说 SO 是执行的所有同步操作 (SA) 的总顺序。但是谈论一次执行的 all SA 有什么意义呢?我不知道这对我有什么用。 all SA 怎么考虑?我理解以下语句的含义:

For each thread t, the synchronization order of the synchronization actions in t is consistent with the program order of t.

语句的用处显而易见,我可以轻松使用。但是 SO 的定义对我来说并不清楚。因此,当涉及到多个线程的 SO 时,我无法弄清楚如何使用它。这让我很担心。你是怎么理解SO的,在写程序的时候又是怎么使用的?

SO 不是您日常处理的事情。它只是构建happens-before关系的基础。

happens-before 关系中涉及一些顺序。

有程序顺序 (PO):因此 loads/stores 在应用任何优化之前由 Java 源代码指定的单个线程执行的顺序。程序顺序为单个线程的所有 loads/stores 创建一个总顺序。但它是偏序,因为它没有对不同线程的 loads/stores 进行排序。

然后是同步顺序 (SO):它是所有同步操作(lock acquire/release、volatile load/store 等)的总顺序。因为每个同步动作都是原子的,所以它们会自动形成一个总序。所以它甚至会命令释放锁A和获取锁B。SO与PO一致。

然后我们有同步 (SW) 顺序:SW 是 SO 的子顺序,因此它只捕获同步关系。例如。如果 X 的易失性写入在 SO 中 X 的易失性读取之前,则同步关系将对这些 2 进行排序。它实际上将对 X 的写入和所有后续的 X 读取进行排序。但与 SO 不同的是,它将不订购例如X的易失性写入和Y的易失性读取。SW也将与PO一致。

happens-before 顺序定义为 PO 和 SW 并集的传递闭包。

注1: SW 和 SO 需要与 PO 一致的原因是我们不希望 SW/SO 说 a->b 而 PO 说 b->a。因为这会导致 happens-before 关系中的循环,并会由于因果循环而使其无效。

注2: 为什么同步指令会创建总订单?出色地;他们没有。但我们可以假装存在一个总秩序。如果 CPU1 正在执行易失性存储 A,而 CPU2 正在执行易失性存储 B(不同的变量),则 A、B 不可能存在因果关系,因此 A-/->B 和 B-/->A(不'之前发生)。所以我们现在有 2 个动作,它们彼此之间没有顺序。很酷的是我们可以选择任何顺序,因为没有人可以证明不是这样。一个更技术性的论点是,SO 是 DAG,每个 DAG 总是至少有 1 个拓扑排序,这是一个总顺序。

补充 :SC-DRF 也需要同步操作的总顺序。

无数据竞争程序 (SC-DRF) 的顺序一致性是 Java 内存模型的主要特征之一。
来自 JLS 17.4.5:

If a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent (§17.4.3).

This is an extremely strong guarantee for programmers. Programmers do not need to reason about reorderings ...

顺序一致性(参见JLS 17.4.3)意味着所有程序操作都有一个总顺序,每个线程都相同。

但是如果同步操作不是完全有序的,那么在某些情况下就会违反 SC-DRF。 其中一个案例就是著名的 IRIW:

    volatile int x, y; // initially zero

    Thread 1 | Thread 2 | Thread 3       | Thread 4
    x = 1;   | y = 1;   | while(x != 1); | while(y != 1);
             |          | int r1 = y;    | int r2 = x;

xyvolatile时,结果(r1=0, r2=0)是不可能的

让我们将 volatile 替换为 setRelease+getAcquire — 我们只会丢失同步操作的总顺序。
来自 VarHandle:

In addition to obeying Acquire and Release properties, all Volatile operations are totally ordered with respect to each other.

    int x, y; // initially zero, with VarHandles X and Y

    Thread 1               | Thread 2               | Thread 3                        | Thread 4
    X.setRelease(this, 1); | Y.setRelease(this, 1); | while(X.getAcquire(this) != 1); | while(Y.getAcquire(this) != 1);
                           |                        | int r1 = Y.getAcquire(this);    | int r2 = X.getAcquire(this);

现在结果 (r1=0, r2=0) 是可能的
=> Threads 3Threads 4 查看以不同顺序写入 xy
=> 违反 SC,这需要对所有程序操作进行单一总排序,每个线程都相同

因此,SC-DRF 需要同步操作的总顺序。

C++ docs for memory_order中有一个类似的例子。