OpenJDK: Java Memory Order

CPU 内存屏障:https://sf-zhou.github.io/programming/memory_barrier.html

volatile 与内存屏障总结: https://zhuanlan.zhihu.com/p/43526907

X86-64 下仅支持一种指令重排:Store-Load ,即读操作可能会重排到写操作前面,同时不同线程的写操作并没有保证全局可见,例子见《Intel® 64 and IA-32 Architectures Software Developer’s Manual》手册 8.6.1、8.2.3.7 节。要注意的是这个问题只能用 mfence 解决,不能靠组合 sfence 和 lfence 解决。(用 sfence+lfence 组合仅可以解决重排问题,但不能解决全局可见性问题,简单理解不如视为 sfencelfence 本身也能乱序重排)

X86-64 一般情况根本不会需要使用 lfencesfence 这两个指令,除非操作 Write-Through 内存或使用 non-temporal 指令(NT 指令,属于 SSE 指令集),比如 movntdq, movnti, maskmovq,这些指令也使用 Write-Through 内存策略,通常使用在图形学或视频处理,Linux 编程里就需要使用 GNC 提供的专门的函数(例子见参考资料 13:Memory part 5: What programmers can do)。

下面是 GNU 中的三种内存屏障定义方法,结合了编译器屏障和三种 CPU 屏障指令:

#define lfence() __asm__ __volatile__("lfence": : :"memory")
#define sfence() __asm__ __volatile__("sfence": : :"memory")
#define mfence() __asm__ __volatile__("mfence": : :"memory")

代码中仍然使用 lfence()sfence() 这两个内存屏障应该也是一种长远的考虑。按照 Interface 写代码是最保险的,万一 Intel 以后出一个采用弱一致模型的 CPU,遗留代码出问题就不好了。目前在 x86 下面视为编译器屏障即可。

sun.misc.Unsafe

sun.misc.Unsafe 中出现了 loadFence()/storeFence()/fullFence() 三种创建内存屏障的方法。而这个 Unsafe 又转而调用了 jdk.internel.misc.Unsafe 的方法。

jdk.internal.misc.Unsafe

loadFence()/storeFence()/fullFence() 都是 native 方法。loadLoadFence()storeStoreFence() 调用了 loadFence()storeFence() 实现。形如 AB 的 barrier 其实指的是 fence 之前的 A 操作和 fence 之后的 B 操作不可以重排,组合下来共有 4 种。

Java 的 volatile 类型对写带了 release 语义,对读带了 acquire 语义是遵循序列一致性的。C++ 的 volatile 保证了写内存的操作不被优化、禁止了线程内一些操作的重排,但是不保证原子性。

loadFence

类似于 C11 atomic_thread_fence(memory_order_acquire),fence 之前的读和 fence 之后的读写不会重排。即:LoadLoad + LoadStore barrier

storeFence

类似于 C11 atomic_thread_fence(memory_order_release),fence 之前的读写和 fence 之后的写不会重排。即:StoreStore + LoadStore barrier

fullFence

类似于 C11 atomic_thread_fence(memory_order_seq_cst),fence 前后的读写都不会重排。即:loadFence + storeFence + StoreLoad barrier

loadLoadFence

含义是 fence 之前的 load 和 fence 之后的 load 不会重排。但实际上使用了约束性更强的 loadFence() 来代为实现。

storeStoreFence

含义是 fence 之前的 store 和 fence 之后的 store 不会重排。但实际上使用了约束性更强的 storeFence() 来代为实现。

比较:C++ 内存屏障类型

可见文档:https://devdocs.io/cpp/atomic/memory_order

相关文章: C++ Atomic Memory Order

  1. memory_order::relaxed:没有要求
  2. memory_order::consume:类似 acquire,但是只指定了依赖的变量
  3. memory_order::acquire:loadFence = loadLoad + loadStore
  4. memory_order::release:storeFence = storeStore + loadStore
  5. memory_order::acq_rel:fullFence — A read-modify-write operation with this memory order is both an acquire operation and a release operation
  6. memory_order::seq_cst:fullFence — 内存的完全有序,但要注意使用方法,见下面的 Note that

A load operation with this memory order performs an acquire operation, a store performs a release operation, and read-modify-write performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order (see below)

之前是 memory_order_ 前缀,现在 C++ 20 把 memory_order 做成了名字空间。

这些内存序选项最后会表现为编译时的指令排序和编译结果中添加的内存屏障指令上。从单个线程的视角来看,所有的操作都是如同代码描述一样有序的,即便是有指令重排也不会影响这个结果(如果影响的话,编译器和处理器就不会去重排了);指令重排会影响其他线程的视角。

release-acquire 能产生同步关系,影响前后的所有值;release-consume 能够产生同步关系,只影响相关值。注意,线程两两的同步关系不能保证三个或更多线程的全局有序关系。

之所以有 acq_rel 这样复杂的内存序选项,可能是因为原子变量的操作并不只有 load/store,还有一些复合操作,比如 read-modify-write,这些操作也需要指定选项。

Note that:

  1. as soon as atomic operations that are not tagged memory_order_seq_cst enter the picture, the sequential consistency guarantee for the program is lost

  2. in many cases, memory_order_seq_cst atomic operations are reorderable with respect to other atomic operations performed by the same thread