美文网首页
volatile关键字

volatile关键字

作者: 归来依旧少女 | 来源:发表于2019-07-07 18:32 被阅读0次

    一、volatile保证内存可见性

    jvm规定所有变量数据需要存放在主内存中,同时各线程又有自己的工作内存(用来做高速缓存)。数据由于cpu与内存速度上的差异,所以线程工作的时候,不是直接操作主内存的数据,而是将数据复制主内存的一份副本到线程自己的工作内存中,操作完再更新主内存。


    线程工作.png

    如图,Thread A将i从主内存复制到自己的变量中,Thread B也复制了一份i。这时候Thread A对i进行了修改,i修改后的值为5,去更新主内存i的值。但是Thread B并没有再次读取主内存的值,而是直接使用自己工作内存的值i=1,这就导致数据不一致了。
    那么如何解决呢?就需要使用volatile了。volatile修饰的变量保证内存可见性,所谓内存可见性就是当一个变量被修改时会立即更新到主内存中。i修改为5了,同时其他线程中的变量i失效了,必须从主内存中重新读取,这时候读取的就是新值5了。

    二、volatile特性

    1. 内存可见性。
    2. 防止指令重排。

    内存可见性已经讲过了,下面讲一下这个防止指令重排。

    int a=1 ; // 1
    int b=2 ; // 2
    int c= a+b ; // 3
    

    上面这段代码我们想象中执行的顺序时1-》2-》3。但其实步骤1和步骤2的关系不大,所以jvm优化后可能执行的顺序是2-》1-》3。jvm指令优化是在不改变最终结果的情况下进行指令重排,在单线程情况下是没有关系的,因为不影响最终结果。而多线程的情况下,可能会发生意想不到的结果。用volatile修饰后,就会按顺序执行了。

    三、volatile使用场景

    3.1 内存可见性,比如状态标识量

    public class Sale {
        private volatile boolean openFlag;
        
        public void setOpenFlag(boolean flag){
            this.openFlag = flag;
        }
        
        public void saleGoods(){
            if(openFlag){
                //开店营业
                ...
            }else{
                //关店调整
                ...
            }
        }
    }
    

    openFlag用volatile修饰,保证获取到的都是最新值。

    3.2 防止指令重排,这个比较经典的就是单例模式

    /**
     * <h1>单例模式</h1>
     */
    public class Singleton {
        private volatile static Singleton singleton;
    
        /**
         * 构造器私有化
         */
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    

    singleton用volatile修饰,防止指令重排。因为 singleton = new Singleton()会分解为三个操作:
    1.分配内存
    2.初始化
    3.变量指向分配的内存地址
    如果不用volatile修饰,因为步骤2和3没有关系,所以可能jvm执行的顺序是1-》3-》2 。所以线程A在执行后singleton已经指向新分配的内存地址,但还没执行初始化这一步。线程B这时候进来了,判断singleton不为null,就返回了,但这时候singleton是未初始化的,这就有问题了。

    参考文档:
    https://mp.weixin.qq.com/s/qXFuilWNOTJGrV9rKQERaA

    相关文章

      网友评论

          本文标题:volatile关键字

          本文链接:https://www.haomeiwen.com/subject/cxptcctx.html