我研究了 x86 硬件中 Java volatile 写入的成本。 我计划在共享内存位置上使用 Unsafe 的 putLongVolatile 方法。查看实现,putLongVolatile get 已转换为 Link 中的 Unsafe_SetLongVolatile然后进入 AtomicWrite,然后是栅栏 Link
简而言之,每个 volatile 写入都会转换为原子写入,然后是完整的栅栏(mfence 或 x86 中的锁定添加指令)。
问题:
1) 为什么 x86 需要 fence() ?由于商店排序,简单的编译器屏障还不够吗?完整的围栏看起来非常昂贵。
2) putLong 代替 Unsafe 的 putLongVolatile 是更好的选择吗?它在多线程情况下能很好地工作吗?
最佳答案
问题 1 的回答:
如果没有完整的栅栏,您就无法获得 JMM 所需的顺序一致性。
所以X86提供了TSO。因此,您可以免费获得以下障碍[LoadLoad][LoadStore][StoreStore]。唯一缺少的是 [StoreLoad]。
加载具有获取语义
r1=X
[LoadLoad]
[LoadStore]
存储具有发布语义
[LoadStore]
[StoreStore]
Y=r2
如果你先存储然后加载,你最终会得到这样的结果:
[LoadStore]
[StoreStore]
Y=r2
r1=X
[LoadLoad]
[LoadStore]
问题是加载和存储仍然可以重新排序,因此它不是顺序一致的;这对于 Java 内存模型是强制性的。他们防止这种情况的唯一方法是使用 [StoreLoad]。最合乎逻辑的地方是将其添加到写入中,因为通常读取比写入更频繁。
这可以通过 MFENCE
或 lock addl %(RSP),0
问题 2 的回答:
putLong 的问题在于,不仅 CPU 可以对指令重新排序,编译器也可能以导致指令重新排序的方式更改代码。
示例:如果您要在循环中执行 putLong,编译器可能会决定将写入从循环中拉出,并且该值将不会对其他线程可见。如果您想要一个低开销的单写入器性能计数器,您可能需要看看 putLongRelease/putLongOrdered(oldname)。这将阻止编译器执行上述技巧。以及您免费获得的 X86 上的发布语义。
但是很难为你的第二个问题提供一个万能的解决方案,因为这取决于你的目标是什么。
关于java - volatile 写入的成本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43460725/