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 实例尾部已经初始化完成,也就造成了线程安全问题。


相关文章
|
Python
解决pyinstaller不兼容python-docx的方法
python-docx是一个python的读写word的库,可以用来读写word文档,向word文档里插入表格,但是与pyinstaller不是原生兼容,需要小改一下
1037 0
解决pyinstaller不兼容python-docx的方法
|
6月前
|
运维 Kubernetes Cloud Native
云栖实录 | 智能运维:云原生大规模集群GitOps实践
云栖实录 | 智能运维:云原生大规模集群GitOps实践
196 1
|
12月前
|
网络协议 网络虚拟化
接收网络包的过程——从硬件网卡解析到IP
【9月更文挑战第18天】这段内容详细描述了网络包接收过程中机制。当网络包触发中断后,内核处理完这批网络包,会进入主动轮询模式,持续处理后续到来的包,直至处理间隙返回其他任务,从而减少中断次数,提高处理效率。此机制涉及网卡驱动初始化时注册轮询函数,通过软中断触发后续处理,并逐步深入内核网络协议栈,最终到达TCP层。整个接收流程分为多个层次,包括DMA技术存入Ring Buffer、中断通知CPU、软中断处理、以及进入内核网络协议栈等多个步骤。
|
11月前
|
存储 编解码 Python
Python 操作 MP4 文件
Python 操作 MP4 文件
217 0
|
数据可视化 关系型数据库 MySQL
Apache Superset 1.2.0教程 (三)—— 图表功能详解
通过之前章节的学习,我们已经成功地安装了superset,并且连接mysql数据库,可视化了王者英雄的数据。使用的是最简单Table类型的图表,但是superset还支持非常多的图表类型。 本文我们将对各种图表类型进行逐一的演示,文章较长。
1199 0
Apache Superset 1.2.0教程 (三)—— 图表功能详解
|
缓存 安全 Java
Java Stream 流详解
Java Stream(流)是Java 8引入的一个强大的新特性,用于处理集合数据。它提供了一种更简洁、更灵活的方式来操作数据,可以大大提高代码的可读性和可维护性。本文将详细介绍Java Stream流的概念、用法和一些常见操作。
460 0
|
数据采集 Kubernetes 监控
Kubernetes 文件采集实践:Sidecar + hostPath 卷
在Kubernetes 日志查询分析实践中,我们介绍了如何通过 DaemonSet 方式部署 logtail 并采集标准输出/文件两种形式的数据。DaemonSet 部署的优势在于其能够尽可能地减少采集 agent 所占用的资源且支持标准输出采集,但因为每个 DaemonSet pod 需要负责 n...
417 1
Kubernetes 文件采集实践:Sidecar + hostPath 卷
|
JSON 关系型数据库 数据库
PostgreSQL merge json的正确姿势
json merge是业务常用的功能,例如网络爬虫,更新合并新爬到的内容。PostgreSQL 9.5 对JSON的类型进行了非常大的功能增强,例如支持合并,按KEY删除,更新KEY VALUE等。以合并为例以右边的值为准,支持嵌套值的合并。 postgres=# select jsonb '
7851 0
|
JavaScript 前端开发 Java
SpringBoot+Vue+Mybatis+Redis实现汽车租赁系统【毕设专用、源码、数据库设计】
SpringBoot+Vue+Mybatis+Redis实现汽车租赁系统【毕设专用、源码、数据库设计】
318 0
SpringBoot+Vue+Mybatis+Redis实现汽车租赁系统【毕设专用、源码、数据库设计】
|
Docker 容器
Docker 安装 Kibana
Docker 安装 Kibana
601 1
Docker 安装 Kibana