volatile原理及使用场景

 

以前一直困惑于volatile到底有什么功能、怎么使用。多方搜索资料,终于理清了头绪,记录一下。

首先,volatile有两个作用:(1)保证变量的可见性(2)防止指令重排序。这两个作用都是通过设置内存屏障实现的。

防止指令重排序这个很好理解,对一个变量可能有多种操作,如果使用操作在赋值操作之前发生,就会导致数据错误甚至空指针等问题。

一直以来最困惑的还是第(1)个作用,volatile是怎么保证变量的可见性的,可见性又是什么呢,为什么需要有可见性?

 

首先,说一下为什么变量会不可见?

Java每个线程工作都有一个工作空间,需要的变量都是从主存中加载进来的。Java内存模型如下(JMM):

volatile原理及使用场景

线程访问一个共享的变量时,都需要先从主存中加载一个副本到自己的工作内存中,经过自己修改后再更新到主存中去。在这个过程中可能出现这种情况:线程A在工作内存中修改了变量1的值,但是还没有写入主存,这档口线程B将变量1加载到自己工作内存中。显然,线程B拿到的不是变量1的最新值了。

        变量可见性就是: 这个变量被任何一个线程修改了,其他线程都能“看见”,也就是能取到变量最新的值。

 

volatile实现可见性的原理

JVM线程工作时的原子性指令有:

    read: 从主存读取一个变量的值的副本到线程的工作内存。

    load:read读来的值,把这个值填充到线程使用的变量中,然后就可以使用了。

    use:要使用一个变量,先发出这个指令。

    assign:赋值,给变量一个新值。

    write:将变量的新值运送到主存中。

    store:将写到主存的新值存到那个变量中。

 上述操作必定是顺序执行的,但可不一定连续,中间可能插入其他指令。为了保证可见性:关键就是保证load、use的执行顺序不被打乱(保证使用变量前一定进行了load操作,从主存拿最新值来),assign、wirte的执行顺序不被打乱(保证赋值后马上就是把值写到主存)。

所以使用内存屏障, CPU指令,可以禁止指令执行乱序:插入一个内存屏障, 相当于告诉CPU和编译器指令顺序先于这个指令的必须先执行,后于这个命令的必须后执行。

    解决第一个导致不可见的因素(更新不及时):内存屏障,对于volatile修饰的变量,读操作时在读指令use插入一条读屏障指令重新从主存加载最新值进来,保证了load、use指令的执行顺序不乱;写操作时在写指令assign插入一条写屏障指令,将工作内存变量的最新值立刻写入主存变量。

 解决第二个因素(指令重排): 由于读写数据时会在之前/后插入一条内存屏障指令,因此volatile可以禁止指令重排序。

 

volatile使用场景

只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写操作不依赖于当前值。
  • 该变量没有包含在具有其他变量的不变式中。

 

文章参考:

https://www.cnblogs.com/zhuawang/p/4197844.html

https://www.cnblogs.com/shen-qian/p/11250805.html