volatile 关键字

 转载自:https://javadoop.com/post/java-memory-model#toc10


volatile 的作用,记住两点:内存可见性和禁止指令重排序。


volatile 的内存可见性

我们还是用 JMM 的主内存和本地内存抽象来描述,这样比较准确。还有,并不是只有 Java 语言才有 volatile 关键字,所以后面的描述一定要建立在 Java 跨平台以后抽象出了内存模型的这个大环境下。

还记得 synchronized 的语义吗?进入 synchronized 时,使得本地缓存失效,synchronized 块中对共享变量的读取必须从主内存读取;退出 synchronized 时,会将进入 synchronized 块之前和 synchronized 块中的写操作刷入到主存中。

volatile 有类似的语义,读一个 volatile 变量之前,需要先使相应的本地缓存失效,这样就必须到主内存读取最新值,写一个 volatile 属性会立即刷入到主内存。所以,volatile 读和 monitorenter 有相同的语义,volatile 写和 monitorexit 有相同的语义。

扫描二维码关注公众号,回复: 168673 查看本文章

volatile 的禁止重排序

大家看下面的双重检查的单例模式,加个 volatile 能解决问题。其实就是利用了 volatile 的禁止重排序功能。

public static Singleton getInstance() {
    if (instance == null) { //
        Singleton temp;
        synchronized (Singleton.class) { //
            temp = instance;
            if (temp == null) { //
                synchronized (Singleton.class) { // 内嵌一个 synchronized 块
                    temp = new Singleton();
                }
                instance = temp; //
            }
        }
    }
    return instance;
}

synchronized 在退出的时候,能保证 synchronized 块中对于共享变量的写入一定会刷入到主内存中。也就是说,上述代码中,内嵌的 synchronized 结束的时候,temp 一定是完整构造出来的,然后再赋给 instance 的值一定是好的。

可是,synchronized 保证了释放监视器锁之前的代码一定会在释放锁之前被执行(如 temp 的初始化一定会在释放锁之前执行完 ),但是没有任何规则规定了,释放锁之后的代码不可以在释放锁之前先执行。

也就是说,代码中释放锁之后的行为 instance = temp 完全可以被提前到前面的 synchronized 代码块中执行,那么重排序问题就又出现了。



volatile 的禁止重排序并不局限于两个 volatile 的属性操作不能重排序,而且是 volatile 属性操作和它周围的普通属性的操作也不能重排序。

之前 instance = new Singleton() 中,如果 instance volatile 的,那么对于 instance 的赋值操作(赋一个引用给 instance 变量)就不会和构造函数中的属性赋值发生重排序,能保证构造方法结束后,才将此对象引用赋值给 instance

根据 volatile 的内存可见性和禁止重排序,那么我们不难得出一个推论:线程 a 如果写入一个 volatile 变量,此时线程 b 再读取这个变量,那么此时对于线程 a 可见的所有属性对于线程 b 都是可见的。

volatile 小结

1.volatile 修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值。在并发包的源码中,它使用得非常多。

2.volatile 属性的读写操作都是无锁的,它不能替代 synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁上,所以说它是低成本的。

3.volatile 只能作用于属性,我们用 volatile 修饰属性,这样 compilers 就不会对这个属性做指令重排序。

4.volatile 提供了可见性,任何一个线程对其的修改将立马对其他线程可见。volatile 属性不会被线程缓存,始终从主存中读取。

5.volatile 提供了 happens-before 保证,对 volatile 变量 v 的写入 happens-before 所有其他线程后续对 v 的读操作。

6.volatile 可以使得 long double 的赋值是原子的。


猜你喜欢

转载自blog.csdn.net/adrian_dai/article/details/80179895