Java中的内存屏障是什么
什么是内存屏障
在转载的大佬的文章既生synchronized,何生volatile中,提到了synchronized与volatile的底层实现原理的不同,synchronized本质上是一种阻塞锁,而volatile则是使用了内存屏障来实现。所以在这里对内存屏障进行一个简单的介绍
为了禁止编译器和CPU对代码进行重排序,在编译器和CPU层面上都有对应的指令,这个就是内存屏障。
编译器的内存屏障只是为了告诉编译器不要对指令进行重排序。当编译完了以后,这种内存屏障就消失了,CPU并不会感知到编译器层面的内存屏障。
而CPU的内存屏障则是CPU提供的指令,可供开发者调用。
Linux中的内存屏障
在Linux内核kfifo.c的源代码的一个RingBuffer中,允许一个线程写,一个线程读(只能一写一读),整个代码没有加任何的锁,也没有CAS,但是线程是安全的,这是如何做到的呢?
kififo在修改数据和更新指针之间,通过函数smp_wmb()插入了一个Store Barrier,从而确保了:
- 更新指针的操作,不会被重排序到修改数据之前
- 更新指针的时候,Store Cache被刷新,其他CPU可以看见
JDK中的内存屏障
内存屏障是很底层的概念,对于Java开发者来说,一般用Volatile关键字就足够了。但是从JDK8开始,Java在Unsafe类中提供了三个内存屏障函数。
public final class Unsafe{
***
public native void loadFence();
public native void storeFence();
public native void fullFence();
***
}
这三个屏障并不是最基本的内存屏障。在理论层面,可以把基本的CPU内存屏障分为四种:
- LoadLoad:禁止读和读的重排序
- StoreStore:禁止写与写的重排序
- LoadStore:禁止读和写的重排序
- StoreLoad:禁止写和读的重排序
在JDK9中对JDK定义的三种内存屏障与理论层面划分的四类内存屏障之间的对应进行了说明:
- loadFence = LoadLoad+LoadStore
- storeFence = StoreStore+LoadStore
- fullFence = StoreStore+LoadStore+StoreLoad
由于不同的CPU架构不同,重排序的策略不同,所提供的内存屏障也有差异。
这里举一种实现Volatile语义的一种参考做法:
- 在volatile写操作的前面插入一个StoreStore屏障。保证了volatile写操作不会和之前的写操作从排序
- 在volatile写操作后面插入一个StoreLoad屏障。保证了volatile的写操作不会和之后的读操作重排序
- 在volatile读操作后面插入一个LoadLoad屏障+LoadStore屏障。保证volatile的读操作不会和后面的读操作和写操作重排序