目录
一、为什么要有内存屏障
内存屏障是为了解决因为cpu,高速缓存,主内存出现的时候,导致的可见性和重序性问题。
1、原理解释
因为计算机的运算任务需要CPU和内存相互配合共同完成,其中CPU负责逻辑计算,内存负责数据存储。但是在真正的实际开发中CPU是要与内存进行交互的,但因为内存和CPU的计算速度是有差距的,因此为了提高CPU的利用效率,现代处理器结构都加入了一层读写速度尽可能接近CPU运算速度的高速缓存来作为内存与CPU之间的缓冲:将运算需要使用的数据复制到缓存中,让CPU运算可以快速进行,计算结束后再将计算结果从缓存同步到主内存中,这样处理器就无须等待缓慢的内存读写了。但在高速缓存解决CPU和内存之间速度的矛盾,但是在多CPU系统中也带来了新的问题:可见性问题和重排序问题。
二、Java层面的内存屏障
内存屏障(Memory Barrier)与内存栅栏(Memory Fence)是同一个概念,不同的叫法。而Java的层面 是应用volatile关键字去修饰变量,解决了编译器层面的可见性与重排序问题。
1、Java的屏障类型
Java中有四种类型:LoadLoad Barriers、StoreStore Barriers、LoadStore Barriers、 StoreLoad Barriers。从该四个类型中我们可以看到他们都是有Load和Store的不同排序组成。而这两个指令是来自硬件的内容。
1.1、Load指令和Store指令
Load指令(读屏障):它将内存存储的数据拷贝到处理器的缓存中。
Store指令(写屏障):它主要实现让当前线程写入高速缓存中的最新数据更新写入到内存,让其他线程也可见。
1.2、LoadLoad Barriers
简单的理解就是当有两个Load,一个Load1一个Load2,Load1加载代码要从内存里面读取的数据读取完毕之后,Load2加载代码才能读取数据。
1.3、StoreStore Barriers
理解为当有两个Store,一个是Store1一个是Store2,Store1的写入操作已经把数据写入到内存里面,并且保证Store1的写入操作对其它处理器可见之后,才会对Store2存储代码进行写入操作执行。
1.4、LoadStore Barriers
理解为当有一个Load1和一个Store2,要先保证Load1加载代码要从内存里面读取的数据读取完毕之后,Store2存储代码才会进行写入操作。
1.5、StoreLoad Barriers
理解为当有一个Store1和Load2,要先保证Store1的写入操作已经把数据写入到内存里面,并且确认Store1的写入操作对其它处理器可见,Load2加载代码才从内存里面读取数据。而且因StoreLoad Barriers同时具备其他三个屏障的效果,因此也称之为全能屏障,是目前大多数处理器所支持的,但是相对其他屏障,该屏障的开销相对昂贵的。
2、内存屏障在Volatile关键字里面的作用
在每个volatile写操作前插入StoreStore屏障,这样就能让其他线程修改A变量后,把修改的值对当前线程可见,在写操作后插入StoreLoad屏障,这样就能让其他线程获取A变量的时候,能够获取到已经被当前线程修改的值
在每个volatile读操作前插入LoadLoad屏障,这样就能让当前线程获取A变量的时候,保证其他线程也都能获取到相同的值,这样所有的线程读取的数据就一样了,在读操作后插入LoadStore屏障;这样就能让当前线程在其他线程修改A变量的值之前,获取到主内存里面A变量的的值。