一、什么是Volatie
volatile字面意思"不稳定".用于修饰共享可变变量.volatile关键字表示被修饰的变量的值容易发生变化,因而不稳定.volatile变量的不稳定性意味着对这种变量的读和写操作都必须从高速缓存或者主存中读取,以读取变量的相对新值.因此,volatile变量不会被编译器分配到寄存器进行存储,对volatile变量的读和写操作都是内存访问操作.
二、volatile的作用
volatile关键字常被称为轻量级锁,其作用与锁的作用有相同的地方:保证可见性和有序性和保障long/double型变量读写的原子性.有所不同的是,在原子性方面它仅能保障写volatile变量操作的原子性,但没有锁的排他性;其次,volatile关键字的使用不会引起上下文的切换.因此,volatile更像是一个轻量级简易锁.
三、volatile保障有序性
对于volatile变量的写操作,Java虚拟机会在该操作之前插入一个释放屏障,并在该操作之后插入一个存储屏障.
释放屏障禁止了volatile写操作与该操作之前的任何读、写操作进行重排序,从而保证了volatile写操作之前那的任何读、写操作会优先于volatile写操作被提交,这就保障了读线程对写线程在更新volatile变量前对共享变量所执行的更新操作的感知顺序与相应的源代码顺序一致,即保障了有序性.
存储屏障具有冲刷处理器缓存的作用,因此在volatile变量写操作之后插入的一个存储屏障就使得该存储器前所有操作的结果对其他处理器来说是可同步的.
四、volatile保障可见性
对于volatile变量操作,Java虚拟机会在该操作之前插入一个加载屏障,并在该操作之后插入一个获取屏障.
加载屏障通过冲刷处理器缓存,使其执行线程所在的处理器将其他处理器对共享变量所做的更新同步到该处理器的高速缓存中.读线程执行的加载存储屏障和写线程执行的存储屏障配合在一起时的写线程对volatile变量的写操作以及在此之前所执行的其他内存操作的结果对读线程可见.
获取屏障禁止了volatile读操作之后的任何读、写操作与volatile读操作进行重排序.因此他保障了volatile读操作之后的任何操作开始执行之前,写线程对相关变量的更新已经对当前线程可见.
五、实践:基于双检查锁定的单例模式
public class Singleton {
private static Singleton instance = null;
public Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {操作1
synchronized (Singleton.class) {
if (instance == null) {操作2
instance = new Singleton();操作3
}
}
}
return instance;
}
}
操作3可以分解以下代码:
objRef = alloacte(Singleton.class);子操作1 //分配对象所需的存储空间
invokeConstructor(objRef);子操作2 // 初始化objRef引用的对象
instance = objRef;子操作3 //将对象引用写入共享变量
根据锁的重排序规则,临界区的操作可以在临界区内被重排序.因此JIT编辑器可能将上述的子操作重排序为:子操作1->子操作3->子操作2.即在初始化对象之前将对象的引用写入实例变量instance. 其他线程执行操作1时,可能看到一个未初始化的实例,即实例变量的变量值可能仍然是默认值,而不是构造器中设置的初始值.一个线程执行操作1的时候发现instance不为null,于是就返回了instance变量所引用的实例,而这个实例可能是未初始化完毕的,这就可能导致程序出错!
解决办法:
Singleton加入volatile修饰:volatile能够禁止子操作2重排序到子操作3后.
正确单例模式实现:
public class Singleton {
private static volatile Singleton instance = null;
public Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}