volatile变量的特性

1、保证可见性,但不保证原子性

      当写一个volatile变量时,会把该线程本地内存中的变量强制刷新到主内存中

      写操作会导致其他线程中的缓存无效

2、禁止指令重排

      重排序是指编译器和处理器为了优化程序性能对指令序列进行排序的一种手段,具体遵守以下规则:

      重排序操作不会对存储数据依赖关系的操作进行重排序,比如a=1;b=a;这个指令,由于第二部操作依赖第一步操作,所以是不会被重排序的

      重排序是为了优化性能的,但是不管怎么重排序,单线程下程序的执行结果是不会改变的;多线程的环境下,可能会发生重排序,影响结果,下例先执行status=true再执行a=2.此时线程B会顺利到达4处,而线程A中a=2的这个操作还未执行,所以b=a+1的结果可能等于2

Volatile java指令重排 java volatile 指令重排序_内存屏障

使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则:

a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

b.在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见

volatile不适用的场景

不适合复合操作,列如inc++不是一个原子性操作,由读取,加,赋值三个操作完成

解决方案:

采用synchronized

Volatile java指令重排 java volatile 指令重排序_缓存_02

2、采用Lock

Volatile java指令重排 java volatile 指令重排序_内存屏障_03

 

 Volatile原理解析

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

I. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内

存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

II. 它会强制将对缓存的修改操作立即写入主存;

III. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

单例模式的双重锁为什么要加volatile

Volatile java指令重排 java volatile 指令重排序_volatile_04

需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。instance = new TestInstance();可以分解为3行伪代码

a.memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址

上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。