并发--内存屏障
参考:https://www.jianshu.com/p/43af2cc32f90
内存屏障
- 1.内存屏障是被插入两个CPU指令之间的一种指令,用来禁止处理器指令发生重排序(像屏障一样),从而保障有序性的。另外,为了达到屏障的效果,它也会使处理器写入、读取值之前,将写缓冲器的值写入高速缓存,清空无效队列(清空是指等待无效队列执行完毕),从而“附带”的保障了可见性。
- 2.我们java写的代码最后都会变为指令被cpu执行。
- 3.内存屏障是硬件之上、操作系统或JVM之下,对并发作出的最后一层支持。再向下是是硬件提供的支持;向上是操作系统或JVM对内存屏障作出的各种封装。内存屏障是一种标准,各厂商可能采用不同的实现。
基本屏障类型
tips:举个例子, Store1 Store2 Load1 StoreLoad屏障 Store3 Load2 Load3,该屏障是禁止屏障左侧的store和右侧的load进行指令交换,对于
即Store1可以和Store2互换,Load2可以和Load3互换。不支持屏障前的指令换到屏障后。当屏障前发送store操作,会等待写缓冲刷入到高速缓存或者主内存,在屏障之后的读操作则会将无效队列里面的操作执行清空,cpu直接从其他高速缓存或者主内存获取最新值。
LoadLoad屏障:确保 Load2执行的时候,Load1已经执行完毕了
StoreStore屏障:确保store2操作的时候,store1的操作被其他cpu core发现
LoadStore屏障:确保store1操作的时候,Load1已经执行完成
StoreLoad屏障:确保load1执行的时候,store1的操作被其他cpu core 发现。。它的开销是四种屏障中最大的(冲刷写缓冲器,清空无效化队列)。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
内存屏障的分类
按照可见性保障来分类
-
1.加载屏障(Load Barrier)
-
storeLoad可以作为加载屏障:清空无效化队列,使得处理器读取共享变量时,先从主内存或其他处理器的高速缓存中读取相应变量,更新到自己的缓存中。
-
放在对voliatile变量读取之前
-
2.存储屏障(Store Barrier)
-
StoreLoad屏障可充当存储屏障--冲刷处理器缓存,即将写缓冲器内容写入高速缓存中,使处理器对共享变量的更新写入高速缓存或者主内存中
-
放在对voliatile变量写入之后
这两个屏障一起保证了数据在多处理器之间是可见的。
按照有序性保证来分类
- 1.获取屏障(Acquire Barrier)
- 相当于LoadLoad屏障与LoadStore屏障的组合。在读操作后插入,禁止该读操作与其后的任何读写操作发生重排序;
一般放在对voliatile变量的读取之后
我们锁的获取就是依靠该屏障,即其后续的读写操作要在获取共享数据的所有权之前(我们的AQS获取共享变量state就是因为voliatile会先插入获取屏障),这样可以保证我们获取到锁之后的操作不会在之前操作
- 2.释放屏障(Release Barrier)
- 相当于LoadStore屏障与StoreStore屏障的组合。在一个写操作之前插入,禁止该写操作与其前面的任何读写操作发生重排序。
-
一般插入在对voliatile变量写入之前(参考AQS释放锁)
。 我们AQS释放锁就是操作共享变量,会在释放前插入释放屏障确保我们对state变量写入值之前,所有的读写操作都执行完毕
这两个屏障一起保证了临界区中的任何读写操作不可能被重排序到临界区之外。
volatile(我们的读写锁是基于voliate开发的)
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
synchronize
通过moitorenter和moitorexit两个指令实现
- 0.moitorenter
- 1.LoadBarrier
- 2.AccquireBarrier
- 3.临界区
- 4.Release Barrier
- 5.moitorexit
- 6.Store Barrier
可以发现,与volatile类似,synchronized底层也是通过释放屏障和获取屏障的配对使用保障有序性,加载屏障和存储屏障的配对使用保障可见性。最后又通过锁的排他性保障了原子性。
网友评论