volatile的内存语义

volatile的特性

  • volatile修饰的变量可以禁止指令重排序和保证了内存可见性和单一操作的原子性,类似i++这样的复合操作的原子性保证不了
  • volatile关键字修饰的共享变量进行写操作数,会多出一个lock前缀指令。lock前缀指令其实就相当于一个内存屏障。在多处理器下,会将当前处理器工作内存的数据回写到主内存中,并且这个回写操作会其它线程中缓存该内存地址的数据无效。相当于会在写操作后,发出一个信号给缓存了这个数的线程,告诉它们值更新了,需要从主内存中从新获取
    • JVM底层volatile是采用“内存屏障”来实现的。
  • volatile经常用于两个两个场景:状态标记两、单列模式中的DCL

volatile写-读建立的happens-before关系

  private  int  count;  //普通变量
  private  volatile  boolean falg;  //volatile 修饰的变量
    //写操作
    public  void  writer(){
        count=1;   // 1
        falg=true;  //2
    }
    // 读操作
    public  void reader(){
        if(falg){                   //3
            int  sum=count+1;       // 4
        }
    }
  • 假设有两个线程:线程A调用读方法, 线程B调用写方法
    根据happens-before规则,这个过程的建立分为三类:
  1. 程序次序规则: 1 happens-before 2,3 happens-before 4
  2. volatile规则:2 happens-before 3 。对一个volatile变量的写操作先行发生于后面对这个变量的读操作
  3. 传递规则: 1 happens-before 4 ;

  • 如果falg不是volatile修饰的,那么操作1操作2之间没有数据依赖性,处理器可能会对这两个操作进行重排序,这时线程A正好执行先执行了操作2,然后这时线程B抢先执行了操作3, 发现为true就执行if语句里的代码, 得到值可能就是1,而不是我们所预想的输出sum=2

volatile写-读的内存语义

  • volatile写操作:当对一个volatile共享变量写操作时,JMM会当前线程对应的更新的后的本地内存中的值强制刷新到主内存中
  • volatile读操作:当读一个volatile共享变量时,JMM会把当前线程对应的本地内存标记为无效,然后线程会从主内存中加载最新的值到工作内存中进行操作。
  • 线程A写一个volatile变量,其实就是新城A向接下来要读取这个共享变量的某个线程,发送了一个信号,告诉它我已经修改了共享变量,你的工作内存的值要被标记无效。
  • 线程B读一个volatile变量,其实就是接收了之前线程A发出的修改共享变量的信号。
  • 对一个volatile变量的写操作,随后对这个变量的读操作,其实就是两个线程之间的进行了通讯。

volatile的内存语义的实现

  • 重排序分为编译器重排序和处理器重排序,为了实现volatile内存语义,JMM会分别限制这两种重排序的内型。

    volatilec重排序规则

第一个操作 第二个操作
普通读/写 普通读/写: yes , volatile读 :yes, volatile写 :no,
volatile读 普通读/写: no , volatile读 :no, volatile写 :no,
volatile写 普通读/写: yes , volatile读 :no, volatile写 :no,
  • 当第一个操作为普通变量的读/写时,如果第二个操作是volatile写,则编译器不能重排序这个两个操作。
  • 当第一个操作是volatile读时,第二个操作不管是什么都不能重排序,这个规则确保volatile读之后的操作不会排序的它之前。
  • 当一个操作是volatile写时,第二个操作时volatile读时,不能重排序

为了实现volatile内存语义,编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

  • 在每个volatile写之前插入一个StoreStore屏障
  • 在每个volatile写操作的后面插入一个StoreLoad屏障
  • 在每个volatile读操作的后面插入一个LoadLoad屏障
  • 在每个volatile读操作的后面插入一个LoadStore屏障
    volatile写指令序列示意图

volatile读指令序列示意图


  转载请注明: codeing volatile的内存语义

 上一篇
一条SQL查询语句是如何执行的 一条SQL查询语句是如何执行的
MySQL可以分为Server层和存储引擎层两部分 Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层
2019-04-16
下一篇 
SpringBoot集成prometheus+Grafana监控 SpringBoot集成prometheus+Grafana监控
概述 Prometheus是一个最初在SoundCloud上构建的开源系统监视和警报工具包 。 添加依赖 <dependency> <groupId>org.springframework.boo
2019-04-02
  目录