Java并发编程之volatile

335 阅读3分钟

上一篇文章中我们介绍了 Synchronzied ,接下来我们来介绍 volatile。

我们知道 Java 内存模型有三大特性,有序性、可见性、原子性。

我们首先来谈一谈有序性。

那么什么是有序性呢?Talk is cheap,show me the code 我们直接来看代码。

public static void main(String[] args) {
             int num1=10;//代码1
             int num2=5;//代码2
             num1=num1-num2;//代码3
             num2=num1*num1;//代码4
       }

这段代码的执行顺序一定是代码1、2、3、4 吗?这是不一定的,为什么呢?因为可能会发生指令重排序。指令重排序就是在不影响程序最终结果的前提下,为了提升程序的执行效率,可能会对输入的代码顺序进行优化,造成执行顺序不一定按照代码书写顺序的一种形式。

我们来看看指令重排序的定义,一个必要的条件就是不影响程序的执行结果,那么想想看我们的代码3和代码4的执行顺序能否调换顺序呢?答案自然是否定的,因为代码4需要依赖代码3的内容。

在单线程的情况下,重排序不会影响程序的运行结果,那么在多线程情况下呢?我们来看一看。


//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

首先在线程1中先执行语句2,然后切换到线程2来执行。这个时候线程二认为初始化已经完成,就会进行接下来的操作,但是 context 并没有初始化,所以就会导致代码出错。

接下来是可见性

可见性是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。

我们使用 volatile 关键字可以保证可见性,使得各个线程都可以读取到最新的值

最后是原子性

原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。及时在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。

我们来看一个最简单的例子,代码 i++ 是原子性操作吗?其实 i++ 可以分为3步来看待。

  • 读取 i 值
  • 对 i +1
  • 把新值赋给 i

这其中任何一步发生都可能发生线程安全问题,所以 i ++ 不是原子性操作。

介绍完我们的三大特性,我们回过头再来说  volatile , volatile 关键字可以做到可见性和有序性。也就是可以保证变量获取最新的值以及禁止指令重排序,但是它不能实现原子性,所以 volatile 的使用具有局限性,我们简单地来总结一下它的使用场景。

  • 对变量的写入操作不依赖当前的值,或者你能确定只有单个线程能够更新变量的值
  • 该变量不会和其他状态变量一起纳入不变性条件中
  • 在访问变量时不需要加锁