从硬件层面理解volatile(java)

本文从硬件层面探讨了Java volatile变量的作用,解释了为何存在可见性问题,涉及到缓存行、CPU缓存一致性、MESI协议、store buffer、invalidate queue以及内存屏障。文章指出,MESI协议保证了CPU缓存一致性,但volatile还涉及到Java编译器、JVM、JIT、操作系统和CPU核心等多个层面。volatile通过内存屏障防止指令重排序,确保多线程环境中的可见性。文章最后提到了JSR内存屏障与指令的关系。
摘要由CSDN通过智能技术生成

从硬件层面理解volatile(java)

目录

从硬件层面理解volatile(java)

一、volatile的作用

二、为什么会有可见性问题

1、缓存行

2、cpu的缓存一致性

3、如何保证cpu的缓存一致性

4、为什么还有可见性问题

5、store buffer

6、invalidate queue

7、cpu如何实现内存屏障

三、JSR内存屏障与指令的关系

参考资料


一、volatile的作用

  • 可见性:当程序执行到volatile变量的读或写时,在其前面的操作肯定全部已经执行完毕,且结果已经对后面的操作可见;在其后面的操作肯定还没有执行

  • 禁止指令重排:在CPU、编译器进行指令优化时,不能把volatile变量后面的语句放到其前面执行,也不能把volatile变量前面的语句放到其后面执行

 

二、为什么会有可见性问题

可见性问题,要从cpu的缓存一致性(cache coherence)开始说起

1、缓存行

在讲缓存一致性之前,我们先来说一下缓存行的概念

缓存是分段(line)的,一个段对应一块存储空间,称为缓存行,它是CPU缓存中可分配的最小存储单元,大小为32、64、128字节不等,与CPU的架构有关,通常为64字节。当CPU看到一条读取内存的指令时,它会把内存地址传递给一级数据缓存,一级数据缓存会检查它是否有这个内存地址对应的缓存段,如果没有就把整个缓存段从内存(或高一级的缓存)中加载进来。

 

2、cpu的缓存一致性

cpu的缓存一致性(cache coherence)是一种保证存储在多个缓存中的共享资源数据相同的机制。缓存不一致,是指相同数据在不同的缓存中呈现出不同的表现。

缓存不一致的问题,在多核CPU的系统中,比较容易出现。假设主存有一个x,值为5。核0和核3都从主存中加载x到缓存。此时核0更改x的值为8。此时核3的缓存中的x的值还是5,数据出现了不一致。

 

 

3、如何保证cpu的缓存一致性

缓存一致性的协议有多种,嗅探(snooping)协议比较常用,它的基本思想是:所有的内存传输都发生在一条共享的总线上,所有的处理器都能看到这条总线。缓存本身是独立的,但是内存是共享资源,所有的内存访问都要经过仲裁(同一个指令周期中,只有一个CPU缓存可以读写内存)

CPU缓存不仅仅在做内存传输时才和总线打交道,而是不停在嗅探总线上发生的数据交换,跟踪其他缓存在做什么。当一个缓存行代表它的处理器去读写内存时,其他处理器都会得到通知,它们以此来使自己的缓存保持同步。只要某个处理器一写内存,其他处理器马上知道这块内存在它们的缓存中已失效。

3.1 MESI协议

MESI协议是当前最主流的缓存一致性协议,在MESI协议中,每个缓存行有4个状态,可用2个bit表示,它们分别是:

状态 描述
M(Modified) 这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在本cache中
E(Exclusive) 这行数据有效,数据和内存中的数据一致,数据只存在于本cache中
S(Shared) 这行数据有效,数据和内存中的数据一致,数据存在于多个cache中
I(Invalid) 这行数据无效

只有当缓存行处于E或M状态,处理器才能去写它,也就是说只有在这两种状态下,处理器是独占这个缓存行的。

当处理器想要写某个缓存行时,如果它没有独占权,必须发送一条“我要独占权”的请求给总线,这会通知其他处理器把它们拥有的同一缓存行的拷贝失效。只有在获得独占权之后,处理器才能开始修改数据。并且此时这个处理器知道,这个缓存行只有一份拷贝,在我自己的缓存里,所以不会有冲突

反之,如果其他处理器想要读取这个缓存行(马上就能知道,因为一直在嗅探总线),独占或者修改状态的缓存行必须先回到共享状态。如果是已修改的缓存行,那么还要先把内容写回到内存中。

看懂了下图,就看懂了MESI协议

 

 

4、为什么还有可见性问题

既然已经有了MESI协议,那为什么未用volatile修饰的变量,在不同的线程中,还是有可见性问题呢?

  • MESI协议只是保证了CPU的缓存一致性,volatile是java语言层面给出的保证。它们之间还差着java编译器、java虚拟机、JIT、操作系统、CPU核心

  • 为了优化MESI协议的性能,cpu引入了store buffer、invalidate queue,它们也会导致可见性问题

  • 有些CPU并没有支持MESI协议

 

5、store buffer

 

5.1 为什么引入store buffer

如下图所示,假设cpu0、cpu1的缓存中都有x(值为5),cpu0想要执行x=8。那么cpu0发送Invalidate消息给cpu1,cpu1发送将自己包含x的缓存行置为invalid状态,然后发送Acknowledge给cpu0,然后cpu0才能修改自己缓存中x的值为8,并将缓存行的状态置为modify。从发送Invalidate到接收到Acknowledge消息的这段时间,cpu0在等待,白白浪费了时间。

 

为了解决这个问题,在cpu和它的缓存之间,加上了store buffer。cpu0先将8(x=8)放入到store buffer,然后发出Invalidate消息,再等待Acknowledge消息返回的期间,cpu0可以继续执行下一条指令。当cpu0接收到Acknowledge消息时,可以将8从store buffer取出,刷入到自己的缓存中。

假设cpu0 x=8之后的指令是y=x+1,它将8放入store buffer之后,执行y=x+1,此时的x是从store buffer取(值为8),还是从缓存中取(值为5)?store buffer有值的话,当然先从store buffer中取,这叫做store forwarding。

 

5.2 store buffer带来的问题

store buffer还会带来乱序的问题

假设a,b的初始值为0,a在cpu1的缓存中,b在cpu0的缓存中

cpu0执行的伪代码如下


                
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值