volatile关键字
volatile关键字
synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized。如果一个变量使用volatile,它比使用synchronized的成本更加低,因为它不会引起线程上下文的切换和调度。
计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。
有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再与主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。
volatile在wiki中的解释
当
volatile
用于一个作用域时,Java保证如下:
- (适用于Java所有版本)读和写一个volatile变量有全局的排序。也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。(但是并不保证经常读写volatile作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。
- (适用于Java5及其之后的版本)volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁。
使用
volatile
会比使用锁更快,但是在一些情况下它不能工作。volatile
使用范围在Java5中得到了扩展,特别是双重检查锁定现在能够正确工作。
一个经典例子
代码块1:
public class RecordExample2 {
int a = 0;
boolean flag = false; //0
/**
* A线程执行
*/
public void writer(){
a = 1; // 1
flag = true; // 2
}
/**
* B线程执行
*/
public void read(){
if(flag){ // 3
int i = a + a; // 4
}
}
}
通过前面的学习我们知道,由于存在重排序,在代码块1中,这两个线程执行后在4中得到的结果是不确定的。这时候就可以使用volatile关键字,在0处改为:
volatile boolean flag = false;
volatile 关键字会使 flag 变量对后面的程序可见, 从而保证了程序的正确性。
总结
- 保证可见性、不保证原子性
- 禁止指令重排序
通常来说,使用volatile必须具备以下2个条件:
1.对变量的写操作不依赖于当前值
2.该变量没有包含在具有其他变量的不变式中
参考资料:
有关原子性,可见性和有序性,此文值得一看(此文最后有volatile的经典用法——单例模型中的双重检查。)