volatile是java语言中的一个关键字,经常使用于并发编程,有两个重要的特色:具备可见性,java虚拟机实现会为其知足Happens before
原则;不具有原子性.用法是修饰变量,如:volatile int i
.html
介绍其可见性先从cpu,cpu缓存和内存的关系入手.java
cpu缓存是一种加速手段,cpu查找数据时会先从缓存中查找,若是不存在会从内存中查找,因此若是缓存中数据和内存中数据不一致,cpu处理数据的一致性就没法保证.从机器语言角度来说,有一些一致性协议来保证缓存一致,可是本文主要从抽象角度解释volatile
为什么能保证可见性.对于volatile
变量的赋值,会刷入主内存,而且通知其余cpu核心,大家缓存中的数据无效了,这样全部cpu核心再想对该volatile
变量操做首先会从主内存中从新拉取值.这就保证了对于cpu操做的数据是最新.编程
可是这并不能保证volatile
修饰的变量的原子性.让咱们想一想一个场景,变量volatile int count
存储在内存中,cpu核心1和cpu核心2同时读取该数据,并存入缓存,而后进行count++
操做.count++
实际能够分解为三步:缓存
int tmp = count; tmp = count + 1; count = tmp;
当count = tmp
执行结束,cpu会把count
刷入内存并通知其余cpu缓存无效,若是两个cpu核心同时将其刷入了内存,通知了缓存无效,那么咱们是否是只获得了count = 2
,是否是丢失了一个+1的值.因此不要试图用volatile
保证多步操做的原子性,原子性能够经过synchronized
进行维护.多线程
须要注意一点,long
类型和double
类型的数据长度是64位的,JVM规范容许对于64位类型数据分开赋值,即高位32位和低位32位能够分开赋值,对于这种状况可使用volatile
修饰保证其赋值是一次完成的.可是!!!虽然JVM是这样规定的,绝大多数虚拟机仍是实现了64位数据赋值的原子性,即便不使用volatile
关键字进行修饰也不会出现读取到只赋值一半的64位类型数据,因此没必要要每一个long
和double
变量以前添加volatile
关键字.并发
了解完原理,来经过一段代码感觉下volatile
.app
public class Volatile implements Runnable{ //自增变量i public /*volatile*/ int i = 0; @Override public void run() { while (true){ i++; //不断自增 } } public static void main(String[] args) throws InterruptedException { Volatile vt = new Volatile(); Watcher watcher = new Watcher(); watcher.v = vt; Thread t1 = new Thread(vt); Thread t2 = new Thread(watcher); t1.start(); t2.start(); Thread.sleep(10); //打印 i 和 s System.out.println("Volatile.i = " + vt.i + "\nwatcher.w = " + watcher.monitor); System.exit(0); } } class Watcher implements Runnable{ public Volatile v; public int monitor; @Override public void run() { while (true){ monitor = v.i;//不断将v.i的值赋给s } } }
// 这是未加volatile修饰的输出 Volatile.i = 2517483 watcher.w = 1047805 // 打开volatile注释的输出结果 Volatile.i = 332754 watcher.w = 333354
第一个输出中未加volatile
修饰的i
的值和watcher
读取的值相差太远,ide
第二个输出中相差就很少了.而且i
的值比未加volatile
关键字的值差不少,说明对volatile
变量的赋值消耗会大一些,不过不用在乎,咱们不多对volatile关键字进行不断自增操做,通常都是做为状态或者保证对象完整性,并且volatile
比synchronized
轻量太多了,若是只为了保证可见性,volatile必定是最优选.线程
因为boolean
的赋值是原子性的,因此volatile
布尔变量做为多线程中止标志还简单有效的.code
class Machine{ volatile boolean stopped = false; void stop(){stopped = true;} }
这里要提到单例对象的双重检查锁,对象完整发布也依赖于happens before
原则,有兴趣能够本身去查阅,这个原则是比较啰嗦,能够简单理解为我知足happens before
,那么我以前的代码按顺序执行.
public class Singleton { //单例对象 private static Singleton instance = null; //私有化构造器,避免外部经过构造器构造对象 private Singleton(){} //这是静态工厂方法,用来产生对象 public static Singleton getInstance(){ if(instance ==null){ //同步锁防止屡次new对象 synchronized (Singleton.class){ //锁内非空判断也是为了防止建立多个对象 if(instance == null){ instance = new Singleton(); } } } return instance; } }
这是一个会产生bug的双重检查锁代码,instance = new Singleton()
并非一步完成的,他被分为这几步:
1.分配对象空间; 2.初始化对象; 3.设置instance指向刚刚分配的地址。
下面图中,线程A红色先得到锁,B黄色后进入.
这种状况会出现bug,可是因为volatile
知足happens before
原则,因此会等对象实例化以后再对地址赋值,咱们须要将private static Singleton instance = null;
改为private static volatile Singleton instance = null;
便可.
其实还有几种场景,若是想了解更多建议阅读IBM的技术社区的文章https://www.ibm.com/developerworks/cn/java/j-jtp06197.html