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

摘要:
LoadBarrier:它可以刷新处理器缓存并同步其他处理器对易失性变量的修改结果。可以发现,易失性关键字变量的读写操作基本上是通过内存屏障执行的,它具有以下两种功能:1。防止指令重新排序。例如,ArrayList不适用于易失性内存屏障。为什么?

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

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

上一次对于第一条作用进行了详细的解读了,接下来则来解读一下剩下的两条:防止指令重排序、实现变量的可见性。而这俩其实都是通过一种手段来实现的:内存屏障(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与内存屏障的重要语义详细分析第2张
  • Store Barrier:它的重要作用是刷新处理器的缓存,结果是可以确保该存储屏障之前一切的操作所生成的结果对于其他处理器来说都可见,也就是:
    Java精通并发-volatile与内存屏障的重要语义详细分析第3张

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与内存屏障的重要语义详细分析第4张
  • Acquire Barrier:可以防止上面的volatile读取操作与下面的所有操作语句的指令重排序。
    Java精通并发-volatile与内存屏障的重要语义详细分析第5张

可以发现:

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

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

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

1、防止指令重排序。

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

所以:

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

最后总结一下:

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

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

注意:

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

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

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

再论volatile和锁【面试题】:

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

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

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

monitorenter
.....
monitorexit

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

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

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

总结:

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

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

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

免责声明:文章转载自《Java精通并发-volatile与内存屏障的重要语义详细分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇jmeter接口测试-调用java的jar包-csv参数化请求-BeanShellPreProcessor生成验签作为请求验证参数-中文乱码----实战【小梅哥SOPC学习笔记】设置Eclipse在编译(build)前自动保存源代码文件下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

intellij idea 显示Arraylist 扩容过程 解决not showing null elements

设置显示Arraylist中的null完整查看初始数组大小,扩容过程一、老版本:Settings -> Debugger -> Data Views -> Arrays -> Hide null array elements新版本:Settings -> Debugger -> Data Views -> Java...

OO第一单元——表达式求导——总结

大二下的第一个月就要结束了,OO的第一单元也结束了,因此在这里总结一下我的OO的第一单元的情况。 总体 第一单元有三次(不计寒假pre)作业,分别为:多项式求导,带有幂函数、三角函数的表达式求导 和 带嵌套的表达式求导。 我三次作业的结构都不完全一样,其中第一次和后两次完全不一样,第三次基本沿袭第二次。下面,我review一下我每次的结构。 结构 第一次...

【决策树】— C4.5算法建立决策树JAVA练习

以下程序是我练习写的,不一定正确也没做存储优化。有问题请留言交流。转载请挂连接。 当前的属性为:age income student credit_rating 当前的数据集为(最后一列是TARGET_VALUE): --------------------------------- youth     high   no   fair      no y...

C#(二维数组/集合)

一、二维数组int [,] array = new int[5,3];//有五个一维数组,每一个一维数组有3个元素/打印出来一个“王”这个字string[,] wang = new string[,]{  {" ","■","■","■","■","■"," "}, {" "," "," ","■"," "," "," "}, {" "," "," ","...

java--集合框架

1.ArrayList和Vector的区别? 1.vector 的所有方法都是同步(Synchronized)的,是线程安全的(thread-safe)的,而ArrayList是线程不安全的,线程安全是会影响性能,ArrayList 比vector的性能好 2.当Vector或ArrayList元素超过初始大小时,vector会将容量翻倍,而arrayLi...

jvm主内存与工作内存

一、jvm主内存与工作内存     首先,JVM将内存组织为主内存和工作内存两个部分。     主内存主要包括本地方法区和堆。每个线程都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程私有的栈和对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。   1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共...