getVolatile 和 getAcquire 有什么区别?
What's the difference between getVolatile and getAcquire?
getVolatile
和 getAcquire 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 个:
- 编译器可以重新排序。
- 现代 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/
getVolatile
和 getAcquire 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 个:
- 编译器可以重新排序。
- 现代 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/