就是线程对变量操作时会从主内存中读取到自己的工作内存中,当线程对变量进行了修改后,那么其他已经读取了此变量的线程中的变量副本就会失效,这样其他线程在使用变量的时候,发现已经失效,那么就会去主内存中重新获取,这样获取到的就只最新的值了。
那么volatile这个关键字是如何实现这套机制的呢?因为一台计算机有多台CPU,同一个变量,在多个CPU中缓存的值有可能不一样,那么以谁缓存的值为准呢?
既然大家都有自己的值,那么各个CPU间就产生了一种协议,来保证按照一定的规律为准,来确定共享变量的准确值,这样各个CPU在读写共享变量时都按照协议来操作。
这就是缓存一致性协议。
最著名的缓存一致性协议就是Intel的MESI了,说MESI时,先解释一下,缓存行:
缓存行(cache line):CPU高速缓存的中可以分配的最小存储单位,高速缓存中的变量都是存在缓存行中的。
MESI的核心思想就是,当CPU对变量进行写操作时发现,变量是共享变量,那么就会通知其他CPU中将该变量的缓存行设置为无效状态。当其他CPU在操作变量时发现此变量在的缓存行已经无效,那么就会去主内存中重新读取最新的变量。
那么其他CPU是如何发现变量被修改了的呢?
因为CPU和其他部件的进行通信是通过总线来进行的,所以每个CPU通过嗅探总线上的传播数据,来检查自己缓存的值是不是过期了,当处理器发现自己换成行对应的内存地址被修改后,就会将自己工作内存中的缓存行设置成无须状态,当CPU对此变量进行修改时会重新从系统主内存中读取变量。
Volatile的有序性(禁止指令重排)一般来说,我们写程序的时候,都是要把先代码从上往下写,默认的认为程序是自顶向下顺序执行的,但是CPU为了提高效率,在保证最终结果准确的情况下,是会对指令进行重新排序的。就是说写在前的代码不一定先执行,在后面的也不一定晚执行。
举个例子:
int a = 5; // 代码1 int b = 8; // 代码2 a = a + 4; // 代码3 int c = a + b; // 代码4上面四行代码的执行顺序有可能是
JMM在是允许指令重排序的,在保证最后结果正确的情况下,处理器可以尽情的发挥,提高执行效率。
当多个线程执行代码的时候重排序的情况就更为突出了,各个CPU为了提高自己的效率,有可能会产生竞争情况,这样就有可能导致最终执行的正确性。
所以为了保证在多个线程下最终执行的正确性,将变量用volatile进行修饰,这样就会达到禁止指令重排序的效果(其实也可以通过加锁,还有一些其他已知规则来实现禁止指令重排序,但是我们这里只讨论volatile的实现方式)。
那么volatile是如何实现指令重排序的呢?答案是:内存屏障
内存屏障是一组CPU指令,用于实现对内存操作的顺序限制。
Java编译器,会在生成指令系列时,在适当的位置会插入内存屏障来禁止处理器对指令的重新排序。
volatile会在变量写操作的前后加入两个内存屏障,来保证前面的写指令和后面的读指令是有序的。
volatile在变量的读操作后面插入两个指令,禁止后面的读指令和写指令重排序。
有序性,不仅只有volatile能保证,其他的实现方式也能保证,但是如果每一种实现方式都要了解那对于开发人员来说就比较困难了。
所以从JDK5就出现了happen-before原则,也叫先行发生原则。
先行发生原则总结起来就是:如果一个操作A的产生的影响能被另一个操作B观察到,那么可以说,这个操作A先行发生与操作B。
这里所说的影响包括内存中的变量的修改,调用了方法,发送量消息等。
volatile中的先行发生原则是,对一个volatile变量的写操作,先行发生于后面任何地方对这个变量的读操作。
Volatile无法保证原子性原子性,是指一个操作过程要么都成功,要么都失败,是一个独立的完整的。
就像上面说的,如果多个线程对一个变量进行累加,那么肯定得不到想要的结果,因为累加就不是一个原子操作。