volatile关键字

volatile关键字


synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized。如果一个变量使用volatile,它比使用synchronized的成本更加低,因为它不会引起线程上下文的切换和调度。

计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。

有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再与主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。

volatile在wiki中的解释

volatile用于一个作用域时,Java保证如下:

  1. (适用于Java所有版本)读和写一个volatile变量有全局的排序。也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。(但是并不保证经常读写volatile作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。
  2. (适用于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 变量对后面的程序可见, 从而保证了程序的正确性。

总结

  1. 保证可见性、不保证原子性
  2. 禁止指令重排序

通常来说,使用volatile必须具备以下2个条件:

1.对变量的写操作不依赖于当前值

2.该变量没有包含在具有其他变量的不变式中


参考资料:

有关原子性,可见性和有序性,此文值得一看(此文最后有volatile的经典用法——单例模型中的双重检查。)

死磕java系列

posted @ 2018-09-25 20:00  一把水果刀  阅读(134)  评论(0编辑  收藏  举报