• 浅析Volatile关键字

    2022-01-13
  • 这篇文章主要向大家介绍浅析Volatile关键字,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。
    浅析Volatile关键字

    在java中线程并发中,线程之间通讯方式分为两种:共享内存和消息传递。共享内存指的是多个线程之间共享内存的属性状态;消息传递指的是线程之间发送信息来通讯。在介绍volatile,咱们先了解一下共享内存一些基本概念。java

    Java内存模型(简称JMM)控制线程通讯,能够分为主内存和本地内存,每一个线程拥有一个本地内存。编程

     

    如图,通常主存只有一个,根据线程数不一样,本地内存(又称工做内存)数目也不一样,本地内存是抽象出来的概念,实际上不存在。数据结构

    线程之间经过内存来同步状态能够分为如下几个步骤:多线程

    一、  线程1从主存获取变量x=1,假设此时其余线程x都为1并发

     

    二、  线程1将本地内存中x值修改成2app

    三、  线程1将本地内存中x值放回主存ide

    四、  其余线程同步主存数据spa

    以上,就是理想状态线程之间经过主存通讯状态,保证了内存的可见性。线程

     

    指令重排序

      java程序在运行时,并不会严格按照程序代码编写的顺序执行代码,会对其进行重排序。例如如下例子:3d

     

      在程序执行代码的时候,步骤1和步骤2可能被重排序,致使2可能在1以前先执行,可是重排序的前提是在单线程状况下不会改变运行结果,所以1和2能够重排序,可是3引用了1和2中的变量,所以3不会被重排序到1或者2以前。在单线程状况下,重排序不会影响,但在多线程中会致使结果不可预见,可使用其余方法来保证有序性。

    原子性操做

      前面提到了可见性,有序性和原子性是在并发编程中最常碰见的三个概念。原子性指不可分割的操做。举例说明,在32为JVM中长度小于32为的数据读写都是原子性操做,可是64位数据结构的读写不是原子性操做。例子以下:

    一、  线程1和2中有共同变量x,为Long型

    二、  线程1将x改成LONG_MAX/2

    三、线程1在修改本地内存中的值到主存时,先将Long的前32保存到主存中,此时主存中的x值为LONG_MAX/2的前32为加上LONG_MAX后32为,咱们设为z

     

    四、线程2读取主存中的x值并更新

    五、线程1同步后32位数据到主存中,以后线程3也同步主存数据

      最后能够看到,因为64为数据读写不是原子性的,单只线程2获取的数据为错误数据,若是,线程1同步主存的操做为原子性,那么就不会出现以上状况。

     

    Volatile关键字

    Volatile可以保证程序的原子性,例如将Long或者Double设置为volatile能够避免因为非原子性读写形成的数据不一样步。可是,相似于x = x 1;这种类型的复合操做,volatile依旧服务保证原子性。

    一、  volatile关键字x,线程1将x修改成LONG_MAX/2,主存为z

    二、此时,因为x是volatile,所以线程要从主存读取这个值的时候,并不会主动到主存中获取,而是等到其余线程通知他再去获取。所以,线程1将完整的x值写到主存中。

    三、以后,通知其余线程去获取x的值,以实现数值同步,保证原子性

      因为volatile的值会及时将值刷新到主存中,volatile也保证了可见性。

      那么volatile是否能保证有序性?我的看法是能在必定程度上保证数据的可见性,可是没有了解到有资料明确说明volatile保证了原子性。

      volatile实际上设置了内存屏障,因为内存屏障的存在,保证了多线程下不会因为重排序致使数据不一致的问题。


    可知volatile写以前的操做不会被重排序到以后;volatile读以后的操做不会被重排序到以前;第一个操做是volatile写,第二个操做是volatile读不会重排序。

      JMM采起的内存屏障策略为:

    一、  在volatile写前面插入StoreStore屏障

    二、  在volatile写后面插入StoreLoad屏障

    三、  在volatile读后面插入LoadLoad屏障

    四、  在volatile读后面插入LoadStore屏障

     

    StoreStore屏障保证volatile写以前的普通写刷新到主存当中,避免与前面普通写重排序;

    StoreLoad屏障保证volatile写不会和以后的volatile读写重排序;

    LoadLoad屏障禁止volatile读不会与下面的普通读重排序;

    LoadStore屏障禁止volatile读不会与下面的普通写重排序。

    具体可见p1,p2

     

    P1

     

    P2

     

    一个例子

      以下例子,线程1执行writer方法,线程2执行reader方法,因为JVM会进行重排序,所以可能的执行顺序为:

      一、线程1执行步骤2,此时a的值仍是0

      二、线程2执行步骤3,由于重排序不会在引发单线程结果改变,所以不会先执行4

      三、线程2执行步骤4,i的值为0

      四、线程1执行步骤1

     

    Int a = 0
    Boolean flag =false
    public void writer(){
     a = 1; //1
     flag = true; //2
    public void reader(){
     if(flag){ //3
     int i = a; //4
    }

    可见,因为重排序致使结果改变,修改代码以下:

    Int a = 0
    volatile boolean flag =false
    public void writer(){
     a = 1; //1
     flag = true; //2
    public void reader(){
     if(flag){ //3
     int i = a; //4
    }

    执行步骤以下:

    一、  线程1执行1,由于volatile变量flag前面插入StoreStore屏障,不会重排序

    二、  线程1执行2;

    三、  线程2执行3;

    四、  线程2执行4,i为2;

    结果符合预期。