volatile关键字详解

volatile的三个特点

  1. 保证线程之间的可见性

  2. 禁止指令重排

  3. 不保证原子性

可见性

概念

可见性是多线程场景中才讨论的,它表示多线程环境中,当一个线程修改了共享变量的值,其他线程能够知道这个修改。

为什么需要可见性

缓存一致性问题:

public class Test {    public static void main(String[] args) {        Mythread mythread = new Mythread();        new Thread(() -> {            try {                //延时2s,确保进入while循环                TimeUnit.SECONDS.sleep(2);                //num自增                mythread.increment();                System.out.println("Thread-" + Thread.currentThread().getName() +                        " current num value:" + mythread.num);            } catch (Exception e) {                e.printStackTrace();            }        }, "test").start();        while(mythread.num == 0){             //dead        }        System.out.println("game over!!!");    }}class Mythread{    //不加volatile,主线程无法得知num的值发生了改变,从而陷入死循环    volatile int num = 0;    public void increment(){        ++num;    }}

如上述代码,如果不加volatile,程序运行结果如下

加上volatile关键字后,程序运行结果如下

解决方向:

  • 总线锁:

    一次只有一个线程能通过总线进行通信。(效率低,已弃用)

  • MESI缓存一致性协议,CPU总线嗅探机制(监听机制)

    有volatile修饰的共享变量在编译器编译后进行读写操作时,指令会多一个lock前缀,Lock前缀的指令在多核处理器下会引发两件事情。


    (参考下面两位大佬的博客)

    https://blog.csdn.net/jinjiniao1/article/details/100540277

    https://blog.csdn.net/qq_33522040/article/details/95319946

    • 每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态, 当处理器对这个数据进行修改操作的时候,会重新从系统内存中吧数据读到处理器缓存行里。

    • 处理器使用嗅探技术保证它的内部缓存,系统内存和其他处理器的缓存在总线上保持一致

    • 写一个volatile变量时,JMM(java共享内存模型)会把该线程对应的本地内存中的共享变量值刷新到主内存;

    • 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来从主内存中读取共享变量。

禁止指令重排

指令重排概念

编译器和CPU在保证最终结果不变的情况下,对指令的执行顺序进行重排序。

指令重排的问题

可以与双重检验实现单例模式联系起来看:

首先,一个对象的创建过程可大致分为以下三步:

  1. 分配内存空间

  2. 执行对象构造方法,初始化对象

  3. 引用指向实例对象在堆中的地址

但是在实际执行过程中,CPU可能会对上述步骤进行优化,进行指令重排

序1->3->2,从而导致引用指向了未初始化的对象,如果这个时候另外一个线

程引用了该未初始化的对象(只执行了1->3两步),就会产生异常。

不保证原子性

为什么无法保证

具体例子

public class Test {    public static void main(String[] args) {        Mythread mythread = new Mythread();        for(int i = 0; i < 6666; ++i){            new Thread(() -> {                try {                    mythread.increment();                } catch (Exception e) {                    e.printStackTrace();                }            }, "test").start();        }        System.out.println("Thread-" + Thread.currentThread().getName() +                " current num value:" + mythread.num);    }}class Mythread{    volatile int num = 0;    public void increment(){        ++num;    }}

上述代码的运行结果如下图

可以看到,循环执行了6666次,但最后的结果为6663,说明在程序运行过程中出

现了重复的情况。

解决方案

  1. 使用JUC中的Atomic类(之后会专门写一篇学习笔记进行阐述)

  2. 使用synchronized关键字修饰(不推荐)

volatile保证可见性和解决指令重排的底层原理

内存屏障(内存栅栏)

组成

内存屏障分为两种:Load Barrier 读屏障Store Barrier 写屏障

4种类型屏障

种类 例子 作用
LoadLoad屏障 Load1; LoadLoad; Load2 保证Load1读取操作读取完毕后再去执行Load2后续读取操作
LoadStore屏障 Load1; LoadStore; Store2 保证Load1读取操作读取完毕后再去执行Load2后续写入操作
StoreStore屏障 Store1; StoreStore; Store2 保证Load1的写入对所有处理器可见后再去执行Load2后续写入操作
StoreLoad屏障 Store1; StoreLoad; Load2 保证Load1的写入对所有处理器可见后再去执行Load2后续读取操作

作用

  1. 保证特定操作的执行顺序 

    在每个volatile修饰的全局变量读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障

  2. 保证某些变量的内存可见性

    在每个volatile修饰的全局变量写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障

(0)

相关推荐

  • volatile关键字的作用

    volatile关键字的作用 1.java内存模型. 如上图所示,所有线程的共享变量都存储在主内存中,每个线程都有一个独立的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自 ...

  • C/C++ 中 volatile 关键字详解 | 菜鸟教程

    C/C++ 中 volatile 关键字详解 | 菜鸟教程

  • C\C 中的 struct 关键字详解

    struct关键字是用来定义一个新的类型,这个新类型里面可以包含各种其他类型,称为结构体. 1. 什么是结构体 结构体(struct)是一种自定义的数据类型,就是把一组需要在一起使用的数据元素组合成一 ...

  • Delphi 关键字详解

    absolute //它使得你能够创建一个新变量, 并且该变量的起始地址与另一个变量相同.var Str: string[32]; StrLen: Byte absolute Str;//这个声明指定 ...

  • JavaScript this 关键字详解

    一.前言 this关键字是JavaScript中最复杂的机制之一.它是一个很特别的关键字,被自动定义在所有函数的作用域中.对于那些没有投入时间学习this机制的JavaScript开发者来说,this ...

  • C++ explicit关键字详解

    首先, C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数 ...

  • 「粉丝问答10」C语言关键字static的使用详解

    本文配套视频地址如下: <关键字static的使用详解> 粉丝提问 粉丝问题,总结一下:关键字static的使用方法. 问题 要想搞清楚关键字static的使用方法,必须首先搞清楚,可执行 ...

  • 小词详解 | volatile

    volatile 英 [ˈvɒlətaɪl] 美 [ˈvɑːlətl] TOEFL SAT TEM8 GMAT GRE 外刊例句 Energy companies are keen to produc ...

  • const关键字及其作用(用法),C语言const详解

    const 在实际编程中用得并不多,const 是 constant 的缩写,意思是"恒定不变的"!它是定义只读变量的关键字,或者说 const 是定义常变量的关键字. 说 con ...

  • 从零开始学Java(十四)详解Java中的static关键字(上)

    ✔上一篇Java零基础系列文章我们说到了Java种this关键字的使用,今天继续来说说Java中的static关键字,篇幅较多,分为上下两篇更新,这篇文章主要内容: Java中static关键字 Ja ...