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 组合仅可以解决重排问题,但不能解决全局可见性问题,简单理解不如视为
sfence
和lfence
本身也能乱序重排)
X86-64 一般情况根本不会需要使用
lfence
与sfence
这两个指令,除非操作 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
memory_order::relaxed
:没有要求memory_order::consume
:类似 acquire,但是只指定了依赖的变量memory_order::acquire
:loadFence = loadLoad + loadStorememory_order::release
:storeFence = storeStore + loadStorememory_order::acq_rel
:fullFence — A read-modify-write operation with this memory order is both an acquire operation and a release operationmemory_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:
as soon as atomic operations that are not tagged
memory_order_seq_cst
enter the picture, the sequential consistency guarantee for the program is lostin many cases,
memory_order_seq_cst
atomic operations are reorderable with respect to other atomic operations performed by the same thread