volatile的字面意思为“不稳定的,易变的”。 我们经常能在一些源码中看见其身影,但在日常开发中却又很少用到,我们可以不用,但不可以不知道它,那么本文就来介绍它的作用。本文参考Java并发编程:volatile关键字解析进行解读。
一、保证可见性
首先我们需要知道,当一个线程执行一个运算,需要哪些步骤。如执行i = i + 1。
- 第一步:首先线程读取内存中当前
i的值,假设为0,然后将i = 0复制一份到线程内的高速缓存中。 - 第二步:线程读取高速缓存中的值进行运算,然后将运算的结果
i = 1放入高速缓存中。 - 第三步:最后cpu的高速缓存再把
i = 1刷新到内存,最终序列化(写入)到硬盘上。
在单线程中,这个步骤没什么毛病,主要是在多线程中。举一个不太恰当的例子(后面会解释为什么不恰当),假设有两个线程A、B都在执行,那么当A执行到第二步的时候还没来得及刷新到内存中,此时B执行第一步,将i = 0拿去计算,最后B的执行结果i = 1覆盖了A的执行结果,原本执行两次之后,i的值应该是2的,这却是1,这就导致了缓存不一致的问题。
解决办法有两个:
- 强行只让1个线程去读取执行,自然就不存在不一致的问题。
- 线程之间的高速缓存数据同步。 第一种办法是加锁,通过synchronized和Lock来实现,能够保证任一时刻只有一个线程执行该代码块,从而保证了原子性。但其效率不高,大概就像这样:
第二种办法就是我们的主角volatile关键字的可见性。保证了不同线程对这个变量进行操作时的可见性,一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 那么在上述例子中,当A的高速缓存中i的值发生改变之后,会强制将值立即写入内存,而B刚从内存中取得的值就失效了,需要再次读取,这样就解决了缓存不一致的问题。
JMM定义了线程和主内存之间的抽象关系:每个线程都有一个私有的本地内存(如CPU的高速缓存),本地内存中存储了该线程以读/写共享变量的副本。
二、保证有序性
首先我们需要了解什么是“指令重排序”。处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
这个跟上面的一样,在单线程执行是没问题的,但是到了多线程中,就会发生混乱,最终导致执行错误。而volatile关键字禁止指令重排序。举个例子,在一个纸箱中放入一些乒乓球,当你用手在纸箱中拨动的时候,里面的球会四处滚动,而在中间放入一块隔板,将箱子隔成前后AB两个空间,那么A中的球只能在A中滚动,B中的球只能在B中滚动,而volatile就是那个隔板。volatile修饰的代码能确保当执行到当前这行时,其之前的代码都已执行完成,而其后面的代码不能比它先执行。
三、不能保证原子性
要解决并发问题,必须同时满足三个条件,原子性、可见性、有序性。上面已经介绍过通过volatile修饰之后可以满足可见性与有序性,那么原子性呢?答案是:volatile无法保证原子性。 那么要进行原子操作,就需要借助synchronized、Lock、JUC(Java 5.0 提供了java.util.concurrent包),所以volatile不可替代synchronized关键字。
四、使用场景
使用volatile必须具备以下2个条件:
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量的不变式中
总而言之上面的意思就是,首先要保证具备原子性才有使用volatile的意义。
我们介绍可见性时举的例子其实是不太准确的,因为i = i + 1它不是一个原子操作。这里有三个步骤:
- 获取
i的值 - 执行
i + 1 - 把
i + 1的计算结果赋值给i
上述的每一个操作都是原子性的,但是合在一块,就不再是原子性了,所以用volatile修饰i并不能解决并发问题。
最后附上一个经典的使用它的场景代码,双重校验锁单例,double check。
class Singleton{
//volatile修饰使得具备有序性与可见性
private volatile static Singleton instance = null;
//私有化构造方法,禁止new对象
private Singleton() {}
public static Singleton getInstance() {
//第一次校验,如果不为空直接返回,为空new对象
if(instance==null) {
//确保操作是原子性
synchronized (Singleton.class) {
//第二次校验,是防止在执行第一次if判断的一瞬间,多个线程进入已new过对象了
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}