volatile 可见性与指令重排

Volatile的作用

​ 现在的CPU都是多核,程序可以在多核上并行执行,指令在执行时,数据会从主存拷贝好CPU的各级缓存上,执行完之后在写回到主存上,此时同一个变量可能在两个核上被操作,该变量在两个拷贝分布在两个核上,此时就会出现问题,比如简单的自增操作,就会是你做你的,我做我的,最后结果会偏离预期。

​ 使用volatile修饰共享变量后,每个线程要操作变量时,会把变量拷贝到缓存中,当线程操作变量副本写回主存后,会通过CPU总线嗅探机制告知其他线程该变量副本已经失效,需要从主存中重新获取。

Volatile是怎么实现的可见性

​ 总线嗅探机制:CPU和内存有极大速度差距,如果CPU和内存直接通信,在存取的过程中CPU会一直空闲。所以CPU和内存之间设置的高速缓存,多缓存导致数据的不一致。总线嗅探就是每个CPU通过监听总线上传播的数据,来检查自己的缓存值是否过期,如果处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存状态置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从主内存中把数据读到处理器缓存中。

​ 对任意单个使用 volatile 修饰的变量的读 / 写是具有原子性,但类似于 flag = !flag 这种复合操作不具有原子性。简单地说就是,单纯的赋值操作是原子性的。

什么是指令重排

​ 为了提高性能,在保证单线程下执行结果不变的情况,编译器和处理器通常会对指令进行重排序。

​ 一般重排序分为三种类型:

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

Volatile防止指令重排

内存屏障:内存屏障是一组处理器指令,它的作用是禁止指令重排序和解决内存可见性问题。

JMM把内存屏障分为以下四类
在这里插入图片描述

volatile读和写是如何插入内存屏障规则:

  • 在每个 volatile 读操作的后面插入 LoadLoad 屏障和 LoadStore 屏障。
  • 在每个 volatile 写操作的前后分别插入一个 StoreStore 屏障和一个 StoreLoad 屏障。
    在这里插入图片描述

也就是说,编译器不会对 volatile 读与 volatile 读后面的任意内存操作重排序;编译器不会对 volatile 写与 volatile 写前面的任意内存操作重排序。

总结

  • volatile 修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值;或者作为状态变量,如 flag = ture,实现轻量级同步。
  • volatile 属性的读写操作都是无锁的,它不能替代 synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁上,所以说它是低成本的。
  • volatile 只能作用于属性,我们用 volatile 修饰属性,这样编译器就不会对这个属性做指令重排序。
  • volatile 提供了可见性,任何一个线程对其的修改将立马对其他线程可见。
  • volatile 提供了 happens-before 保证,对 volatile 变量 V 的写入 happens-before 所有其他线程后续对 V 的读操作。
  • volatile 可以使纯赋值操作是原子的,如 boolean flag = true; falg = false
  • volatile 可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性。

参考:volatile 关键字,你真的理解吗? - 知乎 (zhihu.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值