getVolatile 和 getAcquire 有什么区别?

What's the difference between getVolatile and getAcquire?

getVolatilegetAcquire when using e.g. AtomicInteger 有什么区别?

PS:与

相关

The source of a synchronizes-with edge is called a release, and the destination is called an acquire.

来自 https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.3

这一切都回到了我们希望如何优化我们的代码。重新排序代码方面的优化。编译器可能会重新排序以进行优化。 getAquire 确保它后面的指令不会在它之前执行。这些指令可能会重新排序,但它们将始终在 getAquire.

之后执行

这与 setRelease(对于 VarHandle)结合使用,其中 setRelease 确保在它之前发生的事情不会被重新排序为在它之后发生。

示例:

Thread1:

    var x = 1;
    var y = 2;
    var z = 3;

    A.setRelease(this, 10)

x、y 和 z 的分配将在 A.setRelease 之前发生,但可能会自行重新排序。

Thread 2:

if (A.getAquire(this) == 10) {
  // we know that x is 1, y is 2 and z = 3
}

这是并发程序的一个很好的用例,您不必在所有事情上推动波动性,而只需要在另一个之前执行一些指令。

对于 getVolatile,变量的处理方式与 Java 中的任何易变变量一样。没有重新排序或优化发生。

这个 video 很高兴看到了解所谓的 "memory ordering modes" 是普通的、不透明的、release/acquire 和易变的。

acquire/release 和 volatile(顺序一致性)之间的主要区别之一可以使用 Dekker 算法来证明。

public void lock(int t) { 
    int other = 1-t; 
    flag[t]=1 
    while (flag[other] == 1) { 
        if (turn == other) { 
            flag[t]=0; 
            while (turn == other); 
            flag[t]=1 
        } 
    } 
} 

public void unlock(int t) { 
    turn = 1-t; 
    flag[t]=0 
}

因此,假设标志的写入是使用发布存储完成的,标志的加载是使用获取加载完成的,那么我们将获得以下顺序保证:

.. other loads/stores
[StoreStore][LoadStore]
flag[t]=1 // release-store
flag[other] // acquire-load
[LoadLoad][LoadStore]
.. other loads/stores

问题在于,较早写入 flag[t] 的内容可能会被稍后加载的 flag[other] 重新排序,结果是 2 个线程可能最终进入临界区。

可以重新排序较早存储和较晚加载到不同地址的原因有 2 个:

  1. 编译器可以重新排序。
  2. 现代 CPU 有存储缓冲区来隐藏内存延迟。由于无论如何最终都会进行存储,因此让 CPU 在缓存写入未命中时停止是没有意义的。

为了防止这种情况发生,需要更强大的内存模型。在这种情况下,我们需要顺序一致性,因为我们不希望发生任何重新排序。这可以通过在store和load之间添加一个[StoreLoad]来实现。

.. other loads/stores
[StoreStore][LoadStore]
flag[t]=1 // release-store
[StoreLoad]
flag[other] // acquire-load
[LoadLoad][LoadStore]
.. other loads/stores

这取决于ISA在哪一方完成;例如在 X86 上,这通常是在写入端完成的。例如。使用 MFENCE(还有其他类似 XCHG 的隐式锁,或者像 JVM 通常那样使用 LOCK ADDL 0 到堆栈指针)。

在 ARM 上,这是在读取端完成的。不需要使用像 LDAPR 这样较弱的负载,而是需要一个 LDAR,这将导致 LDAR 等待直到 STLR 从存储缓冲区中耗尽。

为了更好地阅读,请检查以下内容 link: https://shipilev.net/blog/2014/on-the-fence-with-dependencies/