关于 Java 关键字 volatile 的总结_Geffin的博客-程序员ITS203

技术标签: Java并发编程  volatile  

1 什么是 volatile

volatile 是 Java 的一个关键字,它提供了一种轻量级的同步机制。相比于重量级锁 synchronized,volatile 更为轻量级,因为它不会引起线程上下文的切换和调度。

2 volatile 的两个作用

  1. 可以禁止指令的重排序优化
  2. 提供多线程访问共享变量的内存可见性

3 禁止指令重排

3.1 什么是指令重排

指令重排序是 JVM 为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度,例如将多条指令并行执行或者是调整指令的执行顺序。但是在多线程的情况下,指令重排序可能会带来问题,例如程序执行的顺序可能会被调整。在加上 volatile 关键字之后可以有效解决这个问题。

下面我们举个例子:

double r = 2.1; //(1) 
double pi = 3.14;//(2) 
double area = pi*r*r;//(3)

在代码语句的顺序为 1->2->3,但实际上顺序无论是 1->2->3 还是 2->1->3 对结果并无影响,所以在编译时和运行时可以根据需要对1、2语句进行重排序。

重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:
1 不会对存在数据依赖关系的操作进行重排序
2 重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变

3.2 指令重排带来的问题

我们来看看这个基于双重检验的单例模式:

public class Singleton3 {
    
    private static Singleton3 instance = null;

    private Singleton3() {
    }

    public static Singleton3 getInstance() {
    
        if (instance == null) {
    
            synchronized(Singleton3.class) {
    
                if (instance == null)
                    instance = new Singleton3();// 非原子操作
            }
        }

        return instance;
    }
}

事实上,这个单例模式的实现方式是有问题的,问题在哪呢?问题在于instance = new Singleton3(); 并不是一个原子操作。

我们可以将其抽象成以下几条指令:

memory =allocate();     //1:分配对象的内存空间 
ctorInstance(memory);   //2:初始化对象 
instance =memory;       //3:设置instance指向刚分配的内存地址

可以看到,操作2依赖于操作1,但操作3并不依赖于操作2。所以 JVM 是可以针对它们进行指令的优化重排序的,经过重排序后如下:

memory =allocate();    //1:分配对象的内存空间 
instance =memory;      //3:instance指向刚分配的内存地址,此时对象还未初始化
ctorInstance(memory);  //2:初始化对象

指令重排之后,instance 指向分配好的内存放在了前面,而这段内存的初始化被排在了后面。在线程A执行这段赋值语句,在初始化分配对象之前就已经将其赋值给 instance 引用,恰好另一个线程进入方法判断 instance 引用不为 null,然后就将其返回使用,导致出错。

3.3 禁止指令重排的原理

volatile 关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

内存屏障会确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

对于上面的基于双重检验的单例模式,我们只需对其稍作修改即可令其正确运行。我们已经知道,问题来自于指令重排,那么我们禁止指令重排即可,用 volatile 关键字修饰 instance 变量,使得 instance 在读、写操作前后都会插入内存屏障,避免重排序。完整代码如下:

public class Singleton3 {
    
    private static volatile Singleton3 instance = null;

    private Singleton3() {
    }

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

4 保证内存可见性

4.1 什么是保证内存可见性

Java 支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象以及成员变量分配的内存是在共享内存中的,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是加速程序的执行,这是现代多核处理器的一个显著特性),所以程序在执行过程中,一个线程看到的变量并不一定是最新的。volatile 告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

4.2 实现的具体细节

如果对声明了 volatile 的变量进行写操作,JVM 就会向处理器发送一条 Lock 前缀的指令,将这个变量所在缓存行的数据写回到系统内存。

但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

具体的说,内存可见性也是通过内存屏障实现的,它会执行下面两个操作:

  1. 强制将对缓存的修改操作立即写入主存
  2. 如果是写操作,它会导致其他 CPU 中对应的缓存行无效

5 总结

  1. volatile 提供了一种轻量级的同步机制,在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞
  2. volatile 只能确保可见性,而加锁机制既可以确保可见性又可以确保原子性
  3. volatile 屏蔽掉了 JVM 中必要的代码优化,所以在效率上比较低
  4. 相比 synchronized,虽然 volatile 更简单并且开销更低,但它的同步性较差,而且其使用也更容易出错

参考:Java多线程编程-(11)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力
volatile关键字及其作用
Java中volatile关键字的最全总结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Geffin/article/details/103498300

智能推荐

【综述】盘点卷积神经网络中的池化操作_小白学视觉的博客-程序员ITS203

点击上方“小白学视觉”,选择加"星标"或“置顶”重磅干货,第一时间送达池化操作(Pooling)是CNN中非常常见的一种操作,池化操作通常也叫做子采样(Subsampling)或降采样(Downsampling),在构建卷积神经网络时,往往会用在卷积层之后,通过池化来降低卷积层输出的特征维度,有效减少网络参数的同时还可以防止过拟合现象。主要功能有以下几点:抑制噪声,降低信...

http协议和websocket的简单使用_梦想称为大佬的博客-程序员ITS203_http调用websocket

http是如何通讯的websocket是什么 和http有什么关系websocket是如何实现通讯的Web通讯技术有哪些HTTP基本概念超文本传输协议,是互联网上应用最广泛的一种网络协议,是用于从www服务器传输超文本到本地浏览器的传送协议http协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求响应的模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:http协议无法实现服务器主动向客户端发起消息。这种单向请求的特点,注定了如果服务器有联系的状态变

黑马程序员---理解三层架构_linqixiang1212的博客-程序员ITS203_黑马三层架构

----------------------Windows Phone 7手机开发、.Net培训、期待与您交流! ---------------------- 在一个ASP.NET Web应用程序解决方案中,并不是只要含有ASPX网页文件、DLL程序集文件、还有数据库文件,就是三层架构的Web应用程序,这样的说法是不对的。也并不是不含有数据库文件,就不是三层架构。还有一个会让初学者误解

error LNK2038: 检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项_Real_BB的博客-程序员ITS203

error:vtkCommon.lib(vtkSmartPointerBase.obj) : error LNK2038: 检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“0”不匹配值“2”(cloudviewer.obj 中)1>vtkCommon.lib(vtkGarbageCollector.obj) : error LNK2038: 检测到“_ITER

Oracle和sql server中复制表结构和表数据的sql语句_weixin_30819163的博客-程序员ITS203

在Oracle和sql server中,如何从一个已知的旧表,来复制新生成一个新的表,如果要复制旧表结构和表数据,对应的sql语句该如何写呢?刚好阿堂这两天用到了,就顺便把它收集汇总一下,供朋友们参考一下了!sql server中复制表结构和表数据的sql语句的写法,分别如下1.复制表的内容到一新表select*into新表名from原表名2.复制表...

UML学习入门就这一篇文章_学无止境--有分享有梦想的博客-程序员ITS203_uml

1.1 UML基础知识扫盲UML这三个字母的全称是Unified Modeling Language,直接翻译就是统一建模语言,简单地说就是一种有特殊用途的语言。你可能会问:这明明是一种图形,为什么说是语言呢?伟大的汉字还不是从图形(象形文字)开始的吗?语言是包括文字和图形的!其实有很多内容文字是无法表达的,你见过建筑设计图纸吗?里面还不是很多图形,光用文字能表达清楚建筑设计吗?在建筑界,...

随便推点

程序员练级手册_小新酱的博客-程序员ITS203_程序员练级手册

月光博客6月12日发表了《写给新手程序员的一封信》,翻译自《An open letter to those who want to start programming》,我的朋友(他在本站的id是Mailper)告诉我,他希望在酷壳上看到一篇更具操作性的文章。因为他也是喜欢编程和技术的家伙,于是,我让他把他的一些学习Python和Web编程的一些点滴总结一下。于是他给我发来了一些他的心得和经历

软件管理_ChineseLiJie的博客-程序员ITS203_软件管理博客

软件管理文章目录软件管理软件包的途径软件包管理的职责软件包管理的核心功能软件包管理软件包分类软件包管理工具软件安装方式rpm包命名规范rpm包管理什么是rpm包及其作用rpm包安装安装软件包, 需要指定软件包绝对路径在软件包所在目录下可以不指定绝对路径测试一个软件包是否能在该系统上安装如果软件包存在, 强制再次安装安装samba服务需要依赖其他组件, 使用--nodeps可重新强制安装rpm包查...

MTS详解_ruiayLin的博客-程序员ITS203_mts及其结构

ORACLE的连接模式有二,分别是专用服务器模式和共享服务器模式。这两种模式很容易区分。对于专用服务器模式,给每一个SESSION分配一个单独的SERVER PROCESS,直到用户断开连接,才会释放这一块资源。她就好比是我们的的士,在把你送到目的地之前不会为其他人服务的。对于共享服务器模式,这就是我们的公交车了,一辆公交车是可以为多个用户服务的。下面我来详细描述一

《算法导论》堆排序和优先队列_天才XLM的博客-程序员ITS203

堆是一种树形结构,通常基于完全二叉树实现最大堆或最小堆。堆排序最坏的时间复杂度为O(nlogn),和归并排序一样;又和插入排序一样,属于原地排序;堆排序综合了两种算法的优势。堆结构可以用来实现海量数据的Top-N排序,也可以用来实现优先队列(Priority Queue)。

ICCV2019-Gaussian YOLOv3 理解解析_三叔家的猫的博客-程序员ITS203

序言关于YOLOv3相信大家都很熟悉了,如果对YOLOv3还不熟悉的同学可以先去了解一下它的原理,别选择跳级理解,因为本篇文章说到的Gaussian YOLOv3是基于YOLOv3的改进版本,如果有了解过YOLOv3的理论知识,理解起来相对来说要简单一些。论文链接:Gaussian YOLOv3Github代码:Gaussian YOLOv3代码一、YOLOv3存在的问题在以往基于深度学...

天津科技大学计算机西电,学通信工程专业,大学四年该如何规划?谢谢_weixin_39781599的博客-程序员ITS203

学通信工程专业,大学四年该如何规划?谢谢,一、学通信工程专业,大学四年该如何规划?谢谢学好英语计算机等硬本领,其他的自然不在话下。通信工程偏硬件,但是如果有好的编程基础,会对你将来有很大的帮助,也是非常好的优势之一。二、跪求电子信息工程专业大学排名开设电子信息工程专业院校毕业生能力用人单位评价: 本专业毕业生能力被评为A+等级的学校有: 清本文标题:学通信工程专业,大学四年该如何规划?谢谢,在当今...

推荐文章

热门文章

相关标签