• volatile关键字


    Java的volatile关键字在面试的频率中非常高。在这里记下学习心得。(ps:抄袭)

    volatile,从字面上说是易变的、不稳定的,事实上,也确实如此,这个关键字的作用就是告诉编译器,只要是被此关键字修饰的变量都是易变的、不稳定的。那为什么是易变的呢?因为volatile所修饰的变量是直接存在于主内存中的,线程对变量的操作也是直接反映在主内存中,所以说其是易变的。

    什么是主内存?为什么是在主内存中?先看看java的内存模型(JMM)中内存与线程的关系。 

    JMM中的内存分为主内存和工作内存,其中主内存是所有线程共享的,而工作内存是每个线程独立分配的,各个线程的工作内存之间相互独立、互不可见。在线程启动的时候,虚拟机为每个内存分配了一块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要的共享变量的副本,当然这是为了提高执行效率,读副本的比直接读主内存更快。

    那么对于volatile修饰的变量(共享变量)来说,在工作内存发生了变化后,必须要马上写到主内存中,而线程读取到是volatile修饰的变量时,必须去主内存中去获取最新的值,而不是读工作内存中主内存的副本,这就有效的保证了线程之间变量的可见性。

    volatile特性一:内存可见性,即线程A对volatile变量的修改,其他线程获取的volatile变量都是最新的。

    举个栗子:

    volatile boolean flag;
    ...
    while(!flag){
    doSomeThing();
    }

    检查标记判断退出循环

    volatile的例子很难重现,因为只有在对变量读取频率很高的情况下,虚拟机才不会及时写回到主内存,而当频率没有达到虚拟机认为的高频率时,普通变量和volatile是同样的处理逻辑。

    volatile特性二:可以禁止指令重排序

    在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到“半个”单例

    一个懒加载单例模式实现如下:

    class Singleton {
        private static Singleton instance;
        private Singleton(){}
        public static Singleton getInstance() {
            if ( instance == null ) { //这里存在竞态条件
                instance = new Singleton();
            }
            return instance;
        }
    }
    class Singleton {
        private static Singleton instance;
        private Singleton(){}
        public static Singleton getInstance() {
            if ( instance == null ) { //当instance不为null时,仍可能指向一个“被部分初始化的对象”
                synchronized (Singleton.class) {
                    if ( instance == null ) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    看起来”非常完美:既减少了阻塞,又避免了竞态条件。不错,但实际上仍然存在一个问题——当instance不为null时,仍可能指向一个"被部分初始化的对象"

    如果变量没有volatile修饰,程序执行的顺序可能会进行重排序。

    使用volatile关键字的场景

    synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:

      1)对变量的写操作不依赖于当前值

      2)该变量没有包含在具有其他变量的不变式中

      实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

      事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

      下面列举几个Java中使用volatile的几个场景。

    1.状态标记量

    volatile boolean inited = false;
    //线程1:
    context = loadContext();  
    inited = true;            
     
    //线程2:
    while(!inited ){
    sleep()
    }
    doSomethingwithconfig(context);

    2.double check

     class Singleton{
        private volatile static Singleton instance = null;
         
        private Singleton() {
             
        }
         
        public static Singleton getInstance() {
            if(instance==null) {
                synchronized (Singleton.class) {
                    if(instance==null)
                        instance = new Singleton();
                }
            }
            return instance;
        }
    }

    总结:  

    1.volatile可以保证在多线程读取的数据是最新的(这句话是有点问题的,后面会解释)。

    2.volatile还拥有hapen-befor性,happens-before 关系是程序语句之间的排序保证,这能确保任何内存的写,对其他语句都是可见的。

    3.volatile不能保证数据在多线程下的原子性。这是将数据放入处理器的缓存和放入到内存是两个原子操作,当一个线程对某个数据做出改变,先将这个改变后的数据放入缓存中,但这时刚好有另一个线程从内存中读取了该数据,但是已经改变了的数据却没有被放入到内存中,所以另一个线程读取的数据是有问题的。

    本文纯属原创,如有雷同纯属我抄袭:

    https://blog.csdn.net/u011519624/article/details/63686701

    https://www.cnblogs.com/monkeysayhi/p/7654460.html

    http://www.cnblogs.com/dolphin0520/p/3920373.html

  • 相关阅读:
    【BZOJ-1060】时态同步 树形DP (DFS爆搜)
    【BZOJ-1468】Tree 树分治
    【BZOJ-1097】旅游景点atr SPFA + 状压DP
    【BZOJ-3876】支线剧情 有上下界的网络流(有下界有源有汇最小费用流)
    【BZOJ-2502】清理雪道 有上下界的网络流(有下界的最小流)
    【BZOJ-2055】80人环游世界 上下界费用流 (无源无汇最小费用流)
    【BZOJ-3275&3158】Number&千钧一发 最小割
    【BZOJ-4562】食物链 记忆化搜索(拓扑序 + DP)
    【BZOJ-1367】sequence 可并堆+中位数
    【BZOJ-1455】罗马游戏 可并堆 (左偏树)
  • 原文地址:https://www.cnblogs.com/magic101/p/8798285.html
Copyright © 2020-2023  润新知