关键字volatile能够说是Java虚拟机提供的最轻量级的同步机制,当一个变量定义为volatile,它具备内存可见性以及禁止指令重排序两大特性,为了更好地了解volatile关键字,咱们能够先看Java内存模型java
Java内存模型规定了全部的变量都存储在主内存中,每条线程拥有本身的工做内存,工做内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的全部操做(读写)都必须在工做内存中进行,不一样的线程之间没法直接访问对方工做内存的变量。线程、主内存、工做内存关系:面试
以经典的i++为例,线程A从主内存获取变量i值放入到工做内存的变量副本,而后在工做内存中将i+1,最后将新值同步到主内存中。从中咱们能够看出简单的i++,分了3个步骤,能够明显发如今线程A从主内存获取i值步骤后,可能有其余线程同步主内存中变量i的值,当线程A想要将i+1结果同步到主内存时就会出现不正确的结果,这是典型的线程不安全。编程
当一个线程修改了共享变量,其余线程可以当即得知这个修改。Java内存模型经过在变量修改后将新值同步回主内存,volatile变量能保证新值能当即同步到主内存,以及每次使用前当即从主内存刷新(synchronized和final两个关键字也具有)。仍是拿i++为例,volatile修饰的i能够确保,从主存中所获取的变量i必定是最新的。安全
禁止指令重排序,程序执行的顺序按照代码的前后顺序执行。
在执行程序时为了提升性能,编译器和处理器经常会对指令作重排序。
①.编译器重排序:编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序
②.处理器重排序:若是不存在数据依赖性,处理器能够改变语句对应机器指令的执行顺序
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:
架构
1属于编译器重排序,2和3属于处理器重排序。并发
在某些特定场景中,volatile至关于一个轻量级的sychronize,由于不会引发线程的上下文切换,可是使用volatile必须知足两个条件:
①.运算结果并不依赖变量的当前值,或者可以确保只有单一的线程修改变量的值
②.变量不须要与其余的状态变量共同参与不变约束
两个使用场景:
异步
性能
public class VolatileTest { private volatile boolean shutdownRequested;复制代码public void shutdown() { shutdownRequested = true; } public void doWork(){ while (!shutdownRequested) { // 业务逻辑 } } } 复制代码复制代码public void shutdown() { shutdownRequested = true; } public void doWork(){ while (!shutdownRequested) { // 业务逻辑 } } } 复制代码
spa
复制代码public class Singleton { private volatile static Singleton singleton; public static Singleton getInstance() { if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } } 复制代码复制代码public class Singleton { private volatile static Singleton singleton; public static Singleton getInstance() { if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } } 复制代码
从硬件架构上来说,处理器使用写缓冲区来临时保存向内存写入的数据,能够减小对内存总线的占用。虽然写缓冲区有这么多好处,但此操做仅对它所在的处理器可见,这个特性会对内存操做的执行顺序产生重要的影响,因为操做缓冲区是异步操做因此在外面看来,先写后读,仍是先读后写,没有严格的固定顺序。
线程
volatile修饰的变量相对于普通变量会多出一个lock前缀指令,这个操做至关于一个内存屏障(只有一个CPU访问内存时,不须要内存屏障;但若是有两个或更多CPU访问同一块内存,且其中有一个在观测另外一个,就须要内存屏障来保证一致性)。
是否能重排序 | 第二个操做 | |||
第一个操做 | 普通读 | 普通写 | volatile读 | volatile写 |
普通读 | LoadStore | |||
普通写 | StoreStore | |||
volatile读 | LoadLoad | LoadStore | LoadLoad | LoadStore |
volatile写 | StoreLoad | StoreStore |
public class VolatileTest { int a = 0; volatile int var1 = 1; volatile int var2 = 2;大体过程:复制代码void readAndWrite() { int i = var1; //volatile读 int j = var2; //volatile读 a = i + i; //普通读 var1 = i + 1; //volatile写 var2 = j * 2; //volatile写 } 复制代码} 复制代码void readAndWrite() { int i = var1; //volatile读 int j = var2; //volatile读 a = i + i; //普通读 var1 = i + 1; //volatile写 var2 = j * 2; //volatile写 } 复制代码
1.《深刻理解Java虚拟机》
2.占小狼——面试必问的volatile,你了解多少? 3.《Java并发编程的艺术》