volatile 关键字详解(下)

简介: 本文主要是讲解 volatile 关键字的使用,首概括它的三大特征,然后引入 JMM 模型,结尾我们解释了单例模式(懒汉模式)中为什么要用 volatile。

禁止指令重排总结


volatile 实现了禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。


先了解一个概念,内存屏障(Memory Barrier) 又称为内存栅栏,是一个CPU 指令,它有两个作用:


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


2、保证某些变量的内存可见性(利用该特征实现 volatile 的内存可见性)


由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条 Memmory Barrier 则会告诉编译器和 CPU ,不管什么指令都不能和这条 Memory Barrier 指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排优化。内存屏障另外一个作用是强制刷出各种 CPU 的缓存数据,因此任何 CPU 上的吓成都能读取到这些数据是最新版本


image.png


线程安全性获得保障(线程安全访问)


工作内存与主内存同步延迟现象导致的可见性问题


可以使用 synchronized 或者 volatile 关键在解决,他满都可以使一个线程修改后的变量立即对其他的线程可见。


对于指令重排序导致的可见性问题和有序性问题


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


volatile 运用场景


你在那些地方见过 volatile


单例模式 DCL


volatile 关键字的单例 DCL


public class SingletonDemo {
    private static SingletonDemo singletonDemo;
    private SingletonDemo() {
        System.out.println(Thread.currentThread().getName() + "\t  invoke SingletonDemo()");
    }
    public static SingletonDemo getSingleton() {
        if (singletonDemo == null) {
            // 同步代码块加锁
            synchronized (SingletonDemo.class) {
                if (singletonDemo == null) {
                    singletonDemo = new SingletonDemo();
                }
            }
        }
        return singletonDemo;
    }
}


单例模式 volatile 分析


有  volatile 关键字的单例 DCL


public class SingletonDemo {
    private static volatile SingletonDemo singletonDemo;
    private SingletonDemo() {
        System.out.println(Thread.currentThread().getName() + "\t  invoke SingletonDemo()");
    }
    public static SingletonDemo getSingleton() {
        if (singletonDemo == null) {
            // 同步代码块加锁
            synchronized (SingletonDemo.class) {
                if (singletonDemo == null) {
                    singletonDemo = new SingletonDemo();
                }
            }
        }
        return singletonDemo;
    }
}


单例模式总结


DCL 双端检锁, 机制下不一定是线程安全的,原因是有指令重排的存在,加入volatile 可以禁止指令重排


原因在于某一个线程执行到第一次检测,读取到了 instance 不为 null 时,instance 的引用对象 可能没有完全完成初始化。


模拟代码:


instance = new SingletonDemo() 
memory = allocate() //1. 分配内存空间
instance(memory)    //2. 初始化对象
instance = memory   //3.设置 instance 执行刚才分配的内存地址,此时 instance!= null


步骤 2 和步骤 3 不存在数据依赖关系,而且无论重排前还是重排后的执行结果在但线程中并没有发生改变,因此这种重排优化是优化是允许的


instance = new SingletonDemo() 
memory = allocate() //1. 分配内存空间
instance = memory   //3.设置 instance 执行刚才分配的内存地址,此时 instance!= null 但是对象还没有被初始化完成!
instance(memory)    //2. 初始化对象


但是指令重排只会保证串行语句的执行的一致性(单线程),但是并不会关心多线程间的语义一致性。


所以当一条线程访问 instnce 不为 null 时,由于 instance 实例尾部已经初始化完成,也就造成了线程安全问题。


相关文章
|
2月前
|
缓存 编译器
volatile关键字
volatile关键字
|
4月前
|
缓存 编译器 C语言
一起来探讨volatile关键字
在C语言中,volatile是一个关键字,用于告诉编译器不要对被声明为volatile的变量做优化,以确保每次对该变量的读写都直接操作内存。
|
8月前
|
存储 缓存 Java
volatile 关键字说明
volatile 关键字说明
24 0
|
SQL 缓存 Java
Volatile关键字介绍
Volatile关键字介绍
Volatile关键字介绍
|
存储 缓存 Java
volatile关键字再理解
volatile关键字再理解
volatile关键字再理解
|
SQL 缓存 安全
深入理解volatile关键字
深入理解volatile关键字
129 0
深入理解volatile关键字
|
缓存 前端开发 Java
volatile关键字有什么用?
volatile关键字有什么用?
volatile关键字有什么用?
|
缓存 安全 算法
你应该知道的 volatile 关键字(上)
不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能。 首先来看看为什么会出现这个关键字。
你应该知道的 volatile 关键字(下)
不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能。 首先来看看为什么会出现这个关键字。
|
存储 安全 Java
volatile 关键字详解(上)
本文主要是讲解 volatile 关键字的使用,首概括它的三大特征,然后引入 JMM 模型,结尾我们解释了单例模式(懒汉模式)中为什么要用 volatile。
131 0
volatile 关键字详解(上)