博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
volatile如何保证有序性?内存屏障都不知道怎么拿offer呀
阅读量:4035 次
发布时间:2019-05-24

本文共 2297 字,大约阅读时间需要 7 分钟。

在多线程的世界里,一共有三个问题:原子性问题、可见性问题、有序性问题。整个java并发体系也是围绕着如何解决这三个问题来设计的。volatile关键字也不例外,我们都知道它解决了可见性和有序性,但是不能保证原子性。这篇文章也主要基于其中一个特性,也就是研究一下volatile是如何保证有序性的。

一、有序性

1、有序性案例

有序性指的是:程序执行的顺序按照代码的先后顺序执行。我们可以先看一个被列举了一万次的代码:

int i = 0;              boolean flag = false;i = 1;                //语句1  flag = true;          //语句2

按照我们自己常规的想法,顺序应该从上往下依次执行,但是真实情况是:jvm会在真正执行这段代码的时候进行优化,发生指令的重排序。因此不能保证语句1一定在语句2先执行。

2、数据依赖性

上面的例子,你还会发现这样一个特点,就算是发生了指令的重排序,但是最后的结果总是正确的。我们再举一个例子:

int a = 10;    //语句1int r = 2;     //语句2a = a + 3;     //语句3r = a*a;       //语句4

这种情况会发生指令重排序吗?显然不会,原因是处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令2必须用到指令1的结果,那么处理器一定保证指令1在指令2执行。

3、多线程问题

这种数据的依赖性在单线程环境下一点问题没有,因为总能保证数据的正确,但是在多线程环境下就会出现错误。我们再举一个例子:

class Test {
int a = 0; boolean flag = false; public void do1() {
a = 1; // 1 flag = true; // 2 } public void do2() {
if (flag) {
// 3 int i = a * a; // 4 } }}

上面的这段代码由于语句1和语句2没有数据依赖性,因此会发生指令重排。do2只要看到flag为true,就执行。因此可能的顺序是:

(1)语句1先于语句2:语句2->语句3->语句1->语句4。这时候的结果i=1。

(1)语句2先于语句1:语句2->语句3->语句4->语句1。这时候的结果i=0。

现在我们可以看到在多线程环境下如果发生了指令的重排序,会对结果造成影响。

上面一开始提到过,volatile可以保证有序性,也就是可以防止指令重排序。那么它是如何解决的呢?这就是内存屏障。因此我们从内存屏障讲起。

二、内存屏障

1、什么是内存屏障

内存屏障其实就是一个CPU指令,在硬件层面上来说可以扥为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。主要有两个作用:

(1)阻止屏障两侧的指令重排序;

(2)强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

在JVM层面上来说作用与上面的一样,但是种类可以分为四种:

屏障类型 指令示例 说明
LoadLoad Load1; LoadLoad; Load2 保证load1的读操作先于load2执行
StoreStore Store1; StoreStore; Store2 保证store1的写操作先于store2执行,并刷新到主内存
LoadStore Load1; LoadStore; Store2 保证load1的读操作结束先于load2的写操作执行
StoreLoad Store1; StoreLoad; Load2 保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行

2、volatile如何保证有序性?

首先一个变量被volatile关键字修饰之后有两个作用:

(1)对于写操作:对变量更改完之后,要立刻写回到主存中。

(2)对于读操作:对变量读取的时候,要从主存中读,而不是缓存。

OK,现在针对上面JVM的四种内存屏障,应用到volatile身上。因此volatile也带有了这种效果。其实上面提到的这些内存屏障应用的效果,可以是用happen-before来总结归纳。

3、内存屏障分类

内存屏障有三种类型和一种伪类型:

(1)lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。

(2)sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见。

(3)mfence,即全能屏障,具备ifence和sfence的能力。

(4)Lock前缀:Lock不是一种内存屏障,但是它能完成类似全能型内存屏障的功能。

为什么说Lock是一种伪类型的内存屏障,是因为内存屏障具有happen-before的效果,而Lock在一定程度上保证了先后执行的顺序,因此也叫做伪类型。比如,IO操作的指令,当指令不执行时,就具有了mfence的功能。

OK,一句话说完就是内存屏障保证了volatile的有序性。当然,我在知乎等很多平台也看到了从计算机底层角度来分析的。还特地去看了看相关文献。发现这里面要是详细写,不是一两篇就能完成的。

转载地址:http://lpbdi.baihongyu.com/

你可能感兴趣的文章
Android之TelephonyManager类的方法详解
查看>>
android raw读取超过1M文件的方法
查看>>
ubuntu下SVN服务器安装配置
查看>>
MPMoviePlayerViewController和MPMoviePlayerController的使用
查看>>
CocoaPods实践之制作篇
查看>>
[Mac]Mac 操作系统 常见技巧
查看>>
苹果Swift编程语言入门教程【中文版】
查看>>
捕鱼忍者(ninja fishing)之游戏指南+游戏攻略+游戏体验
查看>>
iphone开发基础之objective-c学习
查看>>
iphone开发之SDK研究(待续)
查看>>
计算机网络复习要点
查看>>
Variable property attributes or Modifiers in iOS
查看>>
NSNotificationCenter 用法总结
查看>>
C primer plus 基础总结(一)
查看>>
剑指offer算法题分析与整理(一)
查看>>
剑指offer算法题分析与整理(三)
查看>>
Ubuntu 13.10使用fcitx输入法
查看>>
pidgin-lwqq 安装
查看>>
mint/ubuntu安装搜狗输入法
查看>>
C++动态申请数组和参数传递问题
查看>>