volatile

1.作用

多线程环境下数据获取时的可见性,有序性(禁止指令重排),弱原子性。

可见性:就是一个线程修改了这个变量的值,就会刷新到主内存中,这个写操作会导致其他线程缓存中的值变为无效。

2.如何实现

也就是靠什么实现的这些功能:缓存一致性协议 和 内存屏障

缓存一致性协议,有很多种,常见的就是MESI(修改、独占、共享、无效)。

MESI的小例子:

两个核心共享某个数据,比如a = 0;(S状态),0 号核心修改了a的值,这时候 1 号核心中的 a 的值就无效了,但是 1 号核心如何知道它的 a 的值是无效的呢?是因为 0 号核心发了个广播,传的是无效化指令,核心 1 收到无效化指令之后,要将 a 的值设成无效,并返回一个无效化指令确认,表示已经改了,这时候 0 号核心才将新的 a 的值写到CPU cache中。

但是,传输无效化指令、收到无效化确认响应的过程中,核心 0 不能做任何事情,所以效率低,浪费了很多时间。为了不让核心 0 盲目等待,可以继续对 a 做别的事情,就用了store buffer和invalidate queue结构,核心 0 在收到无效化确认之前,先把 a 的新值写到store buffer中,并可以随意操作。无效化指令广播给核心 1 ,先存到 invalidate queue 中,等待核心 1 完成当前任务,再从队列里取无效化指令,并进行相应值的无效化操作,然后返回无效化确认响应。

但是,store buffer 和 invalidate queue 的存在又导致了缓存的不一致。

所以,用内存屏障

在写操作前,加入 StoreSotre 屏障(在我写之前,保证前面已经写完了),写操作后面加入 StoreLoad 屏障(在读之前,保证先让我写完)

在读操作前,加入 LoadLoad 屏障(在我读之前,保证前面先读完),读操作后面加入 LoadStore屏障(在后面写之前,保证先让我读完)

缓存一致性协议可以使volatile修饰的变量具有 可见性,内存屏障可以 禁止指令重排。

3.不能保证原子性

例子

比如有个共享变量 i ,两个线程都对这个共享变量 i 做 i++ 操作

而 i++ 操作的过程可以分为以下三个步骤:

1.获取变量 i 的值

2.变量 i 加一

3.写回到内存中

但如果线程 1 和 2 都进行到了第 2 步,然后先后写回内存,结果 i 的值是 1 ,而不是 2 。

由于缓存一致性协议MESI 通过Store Buffer 和 Invalidate Queue 两种结构进行加速,但这两种方式又会造成缓存不一致的问题。

所以有了内存屏障,针对 volatile 变量,JVM采用的内存屏障是:

1.volatile 修饰的变量的写操作:在写操作之前加入StoreStore屏障,写操作后加入 StoreLoad屏障

2.volatile 修饰的变量的读操作:在读操作之前加入LoadLoad屏障,读操作后加入 LoadStore屏障

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值