volatile机制 详细解读,原理一步一步分析

volatile 大家都知道,可见性,禁止重排序,不是原子性。

其他的呢?好像没了 紧紧停留在八股文上。!

今天就好好缕缕思路。

正确理解volatile的姿势:单cpu架构->cpu多级cache结构 -> **缓存一致性协议(MESI)-> store buffer和invalidate queue引入 ->造成mesi协议不一致了**-> 内存屏障-->volatile-->mesi协议再次一致

目录

1.单cpu架构

2.多cpu架构+多级缓存cache架构

 3.涉及到缓存cache,那多个缓存之间需要通信,以及跟我们java之间啥关系呢?

4.有了 mesi协议,后续又会有什么问题呢?

5.加入了storebuffer了,后续又出现了什么问题呢?

6.此时可以看到 架构,相当于有了2个队列了。。要想读取准确的值 怎么做呢? cpu 让应用工程师加 写屏障,读屏障 ;但是这个东西是不是很复杂。。。直接加 volatile 就好了,jvm底层会自动加上 不同类型的屏障,来保障数据可见性。


1.单cpu架构

现在已经被淘汰了

2.多cpu架构+多级缓存cache架构

这个是目前主流架构了

比如我现在的电脑如下:

 3.涉及到缓存cache,那多个缓存之间需要通信,以及跟我们java之间啥关系呢?

    3.1)跟我们java之间的关系

        

   3.2)那多个cache之间怎么保存数据同步呢?

        大名鼎鼎的mesi协议就出来了。

  - MESI状态

  ​       M:modified

  ​            cpu拥有cacheline,并且做了修改,但是修改值还没有刷新到主内存

  ​      E: exclusive

  ​            cpu拥有cacheline,但是还没有做修改。

  ​         M,E 重要特性:在所有cpu的cache中,只有唯一的一个cacheline是M或者E状态

  ​    

  ​     S:shared

  ​         所有cpu的cache都对某一个cacheline拥有read,但是不能写

  

  ​     I:无效,失效

  ​          当前某个cacheline数据无效,也就相当于里面没有数据

演示地址:VivioJS MESI help

4.有了 mesi协议,后续又会有什么问题呢?

效率问题!

这里需要知道cpu读取数据都是通过 cacheline进行读取的,也就是说不同的cpu可能同时读取到共享变量a;

比如我需要修改一个共享变量a;那需要先把a读取过来放cache中,后续想改 就会发送invalid 消息,让其他的cache让这个a 失效,其他cpu需要发送 ack消息回去,但是假如此时其他的cpu都忙 ,那岂不是想改a值的cpu需要一直等待???

那怎么解决呢? 所以cpu工程师加入了storebuffer

也就是说 我想改a的指令我先放storebuffer中,等我收到其他cpu的ack消息时候我再推送到cache中;

由此产生了第一个特性,可见性问题,也就是指令重排序问题产生。此时类似加了个mq,导致异步了 后面的指令也可能排到前面去了。

总结:可见性的本质就是队列的异步化。

5.加入了storebuffer了,后续又出现了什么问题呢?

此时storebuffer,相当于典型的消费队列了。此时由于收到invalid ack消息慢,导致大量的写操作入队列,导致队列很快就满了。。。。

怎么办呢?提高ack消息的返回速度。所以cpu工程师又加入了 invalid queue,这样其他cpu收到invalid消息时候 就马上ack回去了。。。后面有空再进行处理消息。

6.此时可以看到 架构,相当于有了2个队列了。。要想读取准确的值 怎么做呢? cpu 让应用工程师加 写屏障,读屏障 ;但是这个东西是不是很复杂。。。直接加 volatile 就好了,jvm底层会自动加上 不同类型的屏障,来保障数据可见性。

volatile底层其实是lock指令前缀,会把storebuffer中内容强刷到cache中,并刷到内存中。

此时注意:volatile 只保障了volatile修饰的变量的 读/写的 原子性,不能保障多指令还是原子性的。比如a=a++;此时就是多个指令,多线程情况下 指令间有可能会插入其他指令导致不是原子性。

备注:x86里面没有invalidqueue组件

深度解析volatile的两大重要特性
1、可见性
每次读volatile变量的时候,读到的总是最新的值(即所有cpu,最后一次写入的操作),就是任
何一个线程的最新写入。
2、禁止指令重排
维护happens-before:
-对volatile变量的写入不能重排到写入之前的操作之前,从而保证别的线程能够看到最新的写入的

-对volatile变量的读操作,不能排到后续的操作之后。
注意:禁止重排并不是禁止所有的重排,只有volatile写入不能向前,读取不能向后。除了这两种
以外,其他重排可以可以允许的。

记忆规则:
1)只要看第二个指令,是不是volatile写,如果是volatile写,那么一定需要加xxstore屏障,xx根
据第一个指令来,如果是读(不管是normal读还是volatile读)那么xx=load,如果是写(不管是
normal写还是volatile写),那么xx=store

2)只要看第一个指令,是不是volatile读,如果是volatile读,那么一定需要加loadxx屏障,xx根
据第二个指令来,如果是读(不管是normal读还是volatile读)那么xx=load,如果是写(不管是
normal写还是volatile写),那么xx=store
3)还有一个特殊的,volatile写后面接一个volatile读。这就是全屏障,一定会加storeload屏障
4)其余都不需要屏障,可以重排序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫大神

有帮助的话,请打赏官人一下哦!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值