getVolatile和getAcquire有什么区别?它们与编程有关。

8
2个回答

7
这一切都与我们想要优化代码有关。 重新排列代码以进行优化。编译器可以重新排列以实现优化。 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
}

这是一个很好的并发程序用例,您不需要在所有地方都使用volatile,只需要在某些指令执行之前执行一些指令即可。

对于getVolatile,该变量被视为Java中的任何volatile变量。没有重新排序或优化发生。

这个视频是一个很好的了解所谓的“内存排序模式”(plain、opaque、release/acquire和volatile)的方式。


2

获取/释放(acquire/release)和易失性(顺序一致性)之间的关键区别可以通过使用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 
}

假设使用 release 存储来编写标志并使用 acquire-load 来加载标志,那么我们将获得以下排序保证:

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

问题在于早期对flag[t]的写入可以与后来对flag[other]的加载重新排序,结果是两个线程可能会进入临界区。
早期存储和后来对不同地址的加载之所以可以重新排序,有两个原因:
1.编译器可能会重新排序它。 2.现代CPU具有存储缓冲区以隐藏内存延迟。由于存储最终都将被完成,没有必要让CPU在缓存写入未命中时停滞。
为了防止这种情况发生,需要更强的内存模型。在这种情况下,我们需要顺序一致性,因为我们不希望任何重新排序发生。这可以通过在存储和加载之间添加[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上,这是在读取端完成的。需要使用LDAR而不是较弱的LDAPR,这将导致LDAR等待直到存储缓冲区中的STLR已排空。

要深入了解,请查看以下链接:https://shipilev.net/blog/2014/on-the-fence-with-dependencies/


有趣。我总是将IRIW可视化...也许我受到Shipilev演示之一的影响。 - Eugene
IRIW(存储原子性)是SC和RC之间的另一个关键区别。 - pveentjer
1
实际上,所有的内存模型都可以归结为PO松弛和存储原子性松弛的组合。 - pveentjer
从SC -> IBM 370:在PO中放松(因此不再需要[StoreLoad])。 从IBM 370 -> TSO:单副本原子到多副本原子(因为加载可以看到存储缓冲区中的加载,而其他人看不到)。 从TSO -> RC:在PO中放松和多副本原子到非多副本原子。 - pveentjer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接