Volatile作用?Volatile底层是如何实现?

Volatile作用?Volatile底层是如何实现?-mikechen

Volatile关键字

Volatile是一个Java语言的类型修饰符,Volatile会涉及到可见性与有序性。

 

Volatile作用

当一个变量定义为 Volatile 之后,将具备两种特性:

1.保证多线程下的可见性

当一个线程修改了这个变量的值,Volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。

如下图所示:

Volatile作用?Volatile底层是如何实现?-mikechen

主内存主要存储的是Java实例对象,工作内存保存了该线程使用到的变量的主内存中的共享变量的副本拷贝。

如果一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,这样就保证了不同线程对这个变量进行操作时的可见性。

 

2.禁止进行指令重排序(即保证有序性)

指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。

指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。

下面看一个非常典型的禁止重排优化的单例模式例子DCL(Double Check Lock双端检锁机制)

package com.mikechen.concurrent.basic;

public class Singleton {

    // 使用private将构造方法私有化,以防外界通过该构造方法创建多个实例
    private Singleton(){
        System.out.println(Thread.currentThread().getName() +" 私有构造调用了");
    }

    // 由于不能使用构造方法创建实例,所以需要在类的内部创建该类的唯一实例
    // 使用static修饰Singleton, 在外界可以通过类名调用该实例   类名.成员名

    private static Singleton instance;

    public     static Singleton getInstance(){
        //加锁前后都做一次判断

        //第一次检测
        if (instance==null){
            //同步
            synchronized (Singleton.class){

                if (instance == null){
                    //多线程环境下可能会出现问题的地方
                    instance = new Singleton();
                }
            }
        }


     return instance;

    }
    
}

运行这段代码我们可能会得到一个匪夷所思的结果:我们获得的单例对象是未初始化的。

为什么会出现这种情况?因为指令重排。

主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

1. 给 singleton 分配内存
2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)

在JVM的即时编译器中存在指令重排序的优化。

也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。

如下图所示:

Volatile作用?Volatile底层是如何实现?-mikechen

如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

可以利用volatile关键字解决,因为Volatile的另一个作用就是禁止重排序优化。

陈睿mikechen

10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

关注「mikechen」公众号,获取更多技术干货!

后台回复面试即可获取《史上最全阿里Java面试题总结》,后台回复架构,即可获取《阿里架构师进阶专题全部合集

评论交流
    说说你的看法