zoukankan      html  css  js  c++  java
  • 对volatile关键字的理解

    volatile关键字的语义

    1.内存可见性

    • 当一个线程对volatile修饰的变量进行写操作时,JMM会立即把线程对应的本地内存中的共享变量的值刷新到主内存
    • 当一个线程对volatile修饰的变量进行读操作时,JMM会立即把该线程对应的本地内存置为无效,从主内存中读取共享变量的值

    2.禁止重排序

    如果volatile变量与普通变量发⽣了重排序,虽然volatile变量能保证内存可⻅性,但是可能导致普通变量读取错误

    JVM通过内存屏障来实现限制处理器的重排序。编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序

    编译器选择了⼀个⽐较保守的JMM内存屏障插⼊策略,这样可以保证在任何处理器平台,任何程序中都能得到正确的volatile内存语义。这个策略是:

    • 在每个volatile写之前插入一个StoreStore屏障
    • 在每个volatile写之后插入一个StoreLoad屏障
    • 在每个volatile读之插入一个LoadLoad屏障
    • 在每个volatile读之后再插入一个LoadStore屏障

    • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
    • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写⼊操作执⾏前,保证Store1的写⼊操作对其它处理器可⻅。
    • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写⼊操作被刷出前,保证Load1要读取的数据被读取完毕。
    • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执⾏前,保证Store1的写⼊对所有处理器可⻅。它的开销是四种屏障中最⼤的(冲刷写缓冲器,清空⽆效化队列)。在⼤多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

    volatile和普通变量重排序规则总结

    1. 如果第一个操作是volatile读,那么不管第二个操作是什么,都不能重排序

    2. 如果第二个操作是volatile写,那么不管第一个操作是什么,都不能重排序

    3. 如果第一个操作是volatile写,第二个操作是volatile读,那么不能重排序

    如果第一个操作是普通变量读,第二个操作是volatile读,那么可以重排序,如下:

    // 声明变量
    int a = 0; // 声明普通变量
    volatile boolean flag = false; // 声明volatile变量
    // 以下两个变量的读操作是可以重排序的
    int i = a; // 普通变量读
    boolean j = flag; // volatile变量读
    

    volatile的用途

    从volatile的内存语义上来看,volatile可以保证内存可⻅性且禁⽌重排序。在保证内存可⻅性这⼀点上,volatile有着与锁相同的内存语义,所以可以作为⼀个“轻量级”的锁来使⽤。

    但由于volatile仅仅保证对单个volatile变量的读/写具有原⼦性,⽽锁可以保证整个临界区代码的执⾏具有原⼦性。

    所以在功能上,锁⽐volatile更强⼤;在性能上,volatile更有优势

    volatile可以用于单例模式的双检锁检查写法

    如果这⾥的变量声明不使⽤volatile关键字,是可能会发⽣错误的。它可能会被重排序:

    instance = new Singleton(); // 第10⾏
    // 可以分解为以下三个步骤
    1 memory=allocate();// 分配内存 相当于c的malloc
    2 ctorInstanc(memory) //初始化对象
    3 s=memory //设置s指向刚分配的地址
    // 上述三个步骤可能会被重排序为 1-3-2,也就是:
    1 memory=allocate();// 分配内存 相当于c的malloc
    3 s=memory //设置s指向刚分配的地址
    2 ctorInstanc(memory) //初始化对象
    

    ⽽⼀旦假设发⽣了这样的重排序,⽐如线程A在第10⾏执⾏了步骤1和步骤3,但是步骤2还没有执⾏完。这个时候线程A执⾏到了第7⾏,它会判定instance不为空,然后直接返回了⼀个未初始化完成的instance!


    参考资料

    本文参考于这里

  • 相关阅读:
    API协议
    执行聚合
    执行过滤
    执行查询
    介绍查询语言
    探索你的数据
    探索你的集群(翻译)
    es6.4基本概念(中文翻译)
    Python3.7.4安装
    elasticsearch常用请求
  • 原文地址:https://www.cnblogs.com/swifthao/p/13721126.html
Copyright © 2011-2022 走看看