首页 > 编程知识 正文

内存屏障的四种类型,java内存模型

时间:2023-05-04 19:37:28 阅读:233754 作者:415

内存屏障是为了解决在cacheline上的操作重排序问题。

作用:

强制cpu将store buffer中的内容写入到cacheline中

强制cpu将invalidate queue中的请求处理完毕

类型

屏障类型

指令示例

说明

LoadLoadBarriers

Load1;LoadLoad;Load2

该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作

StoreStoreBarriers

Store1;StoreStore;Store2

该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作

LoadStoreBarriers

Load1;LoadStore;Store2

确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作

StoreLoadBarriers

Store1;StoreLoad;Load1

该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作.它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令

StoreLoad Barriers同时具备其他三个屏障的效果,因此也称之为全能屏障,是目前大多数处理器所支持的,但是相对其他屏障,该屏障的开销相对昂贵.在x86架构的处理器的指令集中,lock指令可以触发StoreLoad Barriers.

内存屏障在Java中的体现

volatile:

volatile读之后,所有变量读写操作都不会重排序到其前面。

volatile读之前,所有volatile读写操作都已完成。

volatile写之后,volatile变量读写操作都不会重排序到其前面。

volatile写之前,所有变量的读写操作都已完成。

根据JMM规则,结合内存屏障的相关分析:

在每一个volatile写操作前面插入一个StoreStore屏障。这确保了在进行volatile写之前前面的所有普通的写操作都已经刷新到了内存。

在每一个volatile写操作后面插入一个StoreLoad屏障。这样可以避免volatile写操作与后面可能存在的volatile读写操作发生重排序。

在每一个volatile读操作后面插入一个LoadLoad屏障。这样可以避免volatile读操作和后面普通的读操作进行重排序。

在每一个volatile读操作后面插入一个LoadStore屏障。这样可以避免volatile读操作和后面普通的写操作进行重排序。

final:

写 final 域的重排序规则

JMM 禁止编译器把 final 域的写重排序到构造函数之外。

编译器会在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 屏障。这个屏障禁止处理器把 final 域的写重排序到构造函数之外。

读 final 域的重排序规则

在一个线程中,初次读对象引用与初次读该对象包含的 final 域,JMM 禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读 final 域操作的前面插入一个 LoadLoad 屏障。

CAS

在CPU架构中依靠lock信号保证可见性并禁止重排序。

lock前缀是一个特殊的信号,执行过程如下:

对总线和缓存上锁。

强制所有lock信号之前的指令,都在此之前被执行,并同步相关缓存。

执行lock后的指令(如cmpxchg)。

释放对总线和缓存上的锁。

强制所有lock信号之后的指令,都在此之后被执行,并同步相关缓存。

因此,lock信号虽然不是内存屏障,但具有mfence的语义(当然,还有排他性的语义)。

与内存屏障相比,lock信号要额外对总线和缓存上锁,成本更高。

JVM的内置锁通过操作系统的管程实现。由于管程是一种互斥资源,修改互斥资源至少需要一个CAS操作。因此,锁必然也使用了lock信号,具有mfence的语义。

参考

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。