热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java精通并发volatile与内存屏障的重要语义详细分析

在上一次https:www.cnblogs.comwebor2006protectedp12595201.html咱们已经对于volatile关键字的作用进行了一定的了解,这里回顾

在上一次https://www.cnblogs.com/webor2006/protected/p/12595201.html咱们已经对于volatile关键字的作用进行了一定的了解,这里回顾一下:

Java精通并发-volatile与内存屏障的重要语义详细分析 

上一次对于第一条作用进行了详细的解读了,接下来则来解读一下剩下的两条:防止指令重排序、实现变量的可见性。而这俩其实都是通过一种手段来实现的:内存屏障(memory barrier),所以要想搞清楚这这两条,必须得先来理解内存屏障这个概念,所以接下来重点来搞清楚内存屏障这个平常听得比较少的这个概念。

这其实涉及到JIT(Just In Time)的一些功能,在现代化的JVM编译器当中,它会根据我们所写的代码的情况自动的一定程序的优化,其中优化当中就有一个可能就是会对咱们的指令进行一定的修改,比如按照顺序执行了三条指令:1、2、3【对应我们的代码顺序】,但是在编译完之后可能生成的字节码会变成3、2、1,或1、3、2等,也就是对指令进行重排序了,这里用一个简单的例子来直观的看一下指令重排序的大概思想:

int a = 0;
int b = 1;

a++;

重排后可能为:

int a = 0;
a++;
int b = 0;

对于这个重排序其实是编译器为了让我们的程序执行的性能更高而采取的一种优化手段,但是!!!在极端情况下这种指令重排序的优化手段并不是我们需要的,所以此时就需要防止某些指令重排序,而是按我们所编写的代码的顺序来执行。对于指令重排序而言,在单线程环境下肯定是没任何问题的,如果有问题也不可能出现这种优化策略了,重点是在多线程的环境下这种所谓优化的指令重排序策略可能就会产生问题,而这个volatile关键字就具备这种防止指令重排序的功能。

阐述内存屏障(memeory barrier):

volatile写入操作:

这里先来看一个简单代码:

int a = 1;
String s = "Hello";

volatile boolean v = false; //写入操作

此时则会在volatile这句代码之前和之后插入相应的内存屏障:

int a = 1;
String s = "Hello";
内存屏障
volatile boolean v = false; //写入操作
内存屏障

而内存屏障是存在有分类的,这里给内存屏障再细化一下则为:

int a = 1;
String s = "Hello";
内存屏障 (Release Barrier,释放屏障volatile boolean v = false; //写入操作
内存屏障(Store Barrier,存储屏障

很显然这个屏障我们肉眼是看不到的,是借助于volatile来实现的,那对于这俩个屏障对于程序有啥作用呢?下面来解释一下:

  • Release Barrier:防止下面的volatile与上面的所有操作的指令重排序,并可以让在内存屏障之前所发生的读写操作都能立刻的发布到所有的程序当中,其它线程就能立刻看到其修改的结果。啥意思?
    Java精通并发-volatile与内存屏障的重要语义详细分析
  • Store Barrier:它的重要作用是刷新处理器的缓存,结果是可以确保该存储屏障之前一切的操作所生成的结果对于其他处理器来说都可见,也就是:
    Java精通并发-volatile与内存屏障的重要语义详细分析

volatile读取操作:

对于volatile的读操作其内存屏障又是不一样的,下面来看一下:

int a = 1;
String s = "Hello";
内存屏障 (Release Barrier,释放屏障)
volatile boolean v = false; //写入操作
内存屏障(Store Barrier,存储屏障)

boolean v1 = v; //读取操作

int a = 1;
String s = "Hello";

此时由于遇到了volatile的读取操作,则又会产生内存屏障了,它有别于之前看到的写入的屏障,如下:

int a = 1;
String s = "Hello";
内存屏障 (Release Barrier,释放屏障)
volatile boolean v = false; //写入操作
内存屏障(Store Barrier,存储屏障)

内存屏障 (Load Barrier,加载屏障)
boolean v1 = v; //读取操作
内存屏障 (Acquire Barrier,获取屏障)

int a = 1;
String s = "Hello";

那这两种屏障又有何义呢?

  • Load Barrier:可以刷新处理器缓存,同步其他处理器对该volatile变量的修改结果。也就是:
    Java精通并发-volatile与内存屏障的重要语义详细分析
  • Acquire Barrier:可以防止上面的volatile读取操作与下面的所有操作语句的指令重排序。
    Java精通并发-volatile与内存屏障的重要语义详细分析

可以发现:

Java精通并发-volatile与内存屏障的重要语义详细分析

Java精通并发-volatile与内存屏障的重要语义详细分析

对于volatile关键字变量的读写操作,本质上都是通过内存屏障来执行的,而内存屏障兼具了如下两方面的能力:

1、防止指令重排序。

2、实现变量内存的可见性。

所以:

Java精通并发-volatile与内存屏障的重要语义详细分析

最后总结一下:

1、对于读取操作来说,volatile可以确保该操作与其后续的所有读写操作都不会进行指令重排序。

2、对于修改操作来说,valatile可以确保该操作与其上面的所有读写操作都不会进行指令重排序。

注意:

在上面的举例中都是Java的原生数据类型:

Java精通并发-volatile与内存屏障的重要语义详细分析

如果是一个引用类型呢?比如说ArrayList,那对于volatile的内存屏障功效是不起作用的,为啥?因为ArrayList中的读写操作都不是原子的,比如读操作,得先找到元素的地址,然后再进行读取,但是!!如果将ArrayList的引用赋值给另一个volatile的ArrayList,这就可以确保原子操作,也就有了volatile相关的功效了。

再论volatile和锁【面试题】:

在上一次volatile的学习中已经针对它们俩的相同与不同点做了一个阐述,回忆一下:

Java精通并发-volatile与内存屏障的重要语义详细分析

这里由于学到了内存屏障的知识点,所以需要再拉出来进一步阐述一下,对于synchronized代码块而言,对应的字节码指令我们都知道会是如下:

monitorenter
.....
monitorexit

我们知道锁的功能比volatile功能更强大,因为它有排他性,对于volatile它不是有指令重排序和内存可见性的功效,那锁有木有呢?当然有,所以加上这个功效之后的锁背后的形态就会变为:

monitorenter
内存屏障 (Acquire Barrier,获取屏障)//刷新处理器缓存,同步其他处理器对该volatile变量的修改结果,也就是获取最新的值
.....
内存屏障 (Release Barrier,释放屏障)//也就是将处理结果发布出去,刷新处理器缓存
monitorexit

以上就是对于同步锁的一个完整的形态。

总结:

1、volatile关键字自身的劣势:它相比不使用volatile的变量而言,性能有损失,因为对于有volatile的变量,则每次都是会从主内存(高速缓存)中来获取了,而如果不使用volatile的变量,则会直接从寄存器上获取,要明白,寄存器要比内存获取快多的,所以这个关键字不要烂用。

2、volatile相比锁,优点是volatile不存在阻塞,也不会进行用户态到内核态的切换,而锁肯定是要阻塞且会进行用户和内核态的切换;缺点是它不具备锁的排它性。

通过这两篇的总结,我觉得就已经能彻底来理清这个关键字的含义了,真的涉及到的概念还是很难理解的。


推荐阅读
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • Final关键字的含义及用法详解
    本文详细介绍了Java中final关键字的含义和用法。final关键字可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final成员变量表示常量,只能被赋值一次,赋值后值不再改变。文章还讨论了final类和final方法的应用场景,以及使用final方法的两个原因:锁定方法防止修改和提高执行效率。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 电销机器人作为一种人工智能技术载体,可以帮助企业提升电销效率并节省人工成本。然而,电销机器人市场缺乏统一的市场准入标准,产品品质良莠不齐。创业者在代理或购买电销机器人时应注意谨防用录音冒充真人语音通话以及宣传技术与实际效果不符的情况。选择电销机器人时需要考察公司资质和产品品质,尤其要关注语音识别率。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
卡瑙娇管
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有