深入了解volatile、内存屏障与happens-before规则

  • 1、编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
  • 2、指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;
  • 3、内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。
2、原理

我们来看加了volatile前后的代码,用的就是阿里规约提供给我们的双重检查锁的代码。我们分别编译了两次,第一个是没有使用volatile关键字修饰的,第二个是使用volatile关键字来修饰,然后取出他们的的汇编代码(实在是设计的地方太底层,其实这里算是用到了策略模式了)

未使用volatile修饰

0x000000010d29e93b: mov %rax,%r10
0x000000010d29e93e: shr $0x3,%r10
0x000000010d29e942: mov %r10d,0x68(%rsi)
0x000000010d29e946: shr $0x9,%rsi
0x000000010d29e94a: movabs $0xfe403000,%rax
0x000000010d29e954: movb $0x0,(%rsi,%rax,1)

使用volatile修饰

0x0000000114353959: mov %rax,%r10
0x000000011435395c: shr $0x3,%r10
0x0000000114353960: mov %r10d,0x68(%rsi)
0x0000000114353964: shr $0x9,%rsi
0x0000000114353968: movabs $0x10db6e000,%rax
0x0000000114353972: movb $0x0,(%rsi,%rax,1)
0x0000000114353976: lock addl $0x0,(%rsp)

很明显,在movb操作后,加了volatile修饰的汇编代码后面多了一条汇编指令lock addl $0x0,(%rsp),这个操作相当于一个内存屏障,指令重排时不能把后面的指令重排序到内存屏障之前的位置。lock前缀会强制执行原子操作,它的作用是是的本CPU的cache写入了内存,该写入动作会引起别的CPU无效化其cache。所以通过这样一个空操作,可让前面volatile变量的便是对其他CPU可见

从硬件架构上讲,指令重排序是指CPU将多条指令不按程序规定的顺序分开发送给各相应的点,但并不是指令任意重排,CPU需要能正确处理指令,以保障程序能得出正确的执行结果。lock addl $0x0,(%rsp) 指令把修改同步到内存时,意味着所有值钱的操作都已经执行完成,这样便形成了指令重排序无法越过内存屏障的效果。

三、内存屏障

既然指令重排和可见性都依赖了lock,同时lock指令引出了内存屏障,我们就来学习一下什么是内存屏障。

1、定义

内存屏障:保证屏障前的读写指令必须在屏障后的读写指令之前执行,通知被Volatile修饰的值,每次读取都从主存中读取,每次写入都同步写入主存。

内存屏障具体又分为写屏障和读屏障 写屏障(Store Memory Barrier):强制将缓存中的内容写入到缓存中或者将该指令之后的写操作写入缓存直到之前的内容被刷入到缓存中,也被称之为smp_wmb 读屏障(Load Memory Barrier):强制将无效队列(volatile写操作之后失其作废)中的内容处理完毕,也被称之为smp_rmb

屏障类型指令示例说明
LoadLoadBarriersLoad1;LoadLoad;Load2该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作
StoreStoreBarriersStore1;StoreStore;Store2该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作
LoadStoreBarriersLoad1;LoadStore;Store2确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作
StoreLoadBarriersStore1;StoreLoad;Load1该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作.它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令
2、原理

内存屏障在Java中的体现

  • 1、volatile读之后,所有变量读写操作都不会重排序到其前面。
  • 2、volatile读之前,所有volatile读写操作都已完成。
  • 3、volatile写之后,volatile变量读写操作都不会重排序到其前面。
  • 4、volatile写之前,所有变量的读写操作都已完成。

根据JMM规则,结合内存屏障的相关分析得出以下结论

  • 1、在每一个volatile写操作前面插入一个StoreStore屏障。这确保了在进行volatile写之前前面的所有普通的写操作都已经刷新到了内存。
  • 2、在每一个volatile写操作后面插入一个StoreLoad屏障。这样可以避免volatile写操作与后面可能存在的volatile读写操作发生重排序。
  • 3、在每一个volatile读操作后面插入一个LoadLoad屏障。这样可以避免volatile读操作和后面普通的读操作进行重排序。
  • 4、在每一个volatile读操作后面插入一个LoadStore屏障。这样可以避免volatile读操作和后面普通的写操作进行重排序。

如下图所示:

3、as-if-serial语义

但是用了volatile关键字,程序的运行速度必然会受到影响,那么除了volatile关键字以外什么时候不会发生重排序呢?这里就要引入as-if-serial语义。

as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提供并行度),(单线程)程序的执行结果不能被改变。

如果两个操作访问同一个变量,且这两个操作有一个为写操作,此时这两个操作就存在数据依赖性这里就存在三种情况:1. 读后写;2.写后写;3. 写后读,者三种操作都是存在数据依赖性的。如果重排序会对最终执行结果会存在影响,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序。

int a=1;
int b=2;
int c =a+b;

as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。比如上面计算的代码,在单线程中,会让人感觉代码是一行一行顺序执行上,实际上a,b两行不存在数据依赖性可能会进行重排序,即a,b不是顺序执行的。as-if-serial语义使程序员不必担心单线程中重排序的问题干扰他们,也无需担心内存可见性问题。

说到底,as-if-serial语义不过是一种最基础的架构定义,可以类比地球上氧气的比例约为21%。

重排序可以分为两类:

会改变程序执行结果的重排序。

不会改变程序执行结果的重排序。

JMM对这两种不同性质的重排序,采取了不同的策略。

  • 对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
  • 对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM允许进行优化重排序)

volatile就是通过对内存语义的封装实现了对volatile关键字读写时的顺序和可见。保证了我们所谓的多线程下的可见性,但是还是没办法保证多线程下修改数据的同步,因为同步除了有序和可见还需要满足原子性。

四、happens-before规则

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

本人也收藏了一份Java面试核心知识点来应付面试,借着这次机会可以送给我的读者朋友们

目录:

全靠这套面试题,才让我有惊无险美团二面拿offer  (面经解析)

Java面试核心知识点

一共有30个专题,足够读者朋友们应付面试啦,也节省朋友们去到处搜刮资料自己整理的时间!

全靠这套面试题,才让我有惊无险美团二面拿offer  (面经解析)

Java面试核心知识点

已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了

全靠这套面试题,才让我有惊无险美团二面拿offer  (面经解析)

外链图片转存中…(img-gQhmM4bw-1710426714850)]

Java面试核心知识点

已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了

[外链图片转存中…(img-pUH63T39-1710426714850)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值