缓存一致性协议(MESI)的简单认识

产生背景

前面在说volatile关键字的时候大体讲过,硬件层面由于CUP和内存运行速度差距很大,所以就引入了高速缓存,如下图:
CPU高速缓存结构模型
而高速缓存的出现,就导致了数据可见性的问题,如下图
数据可见性模型
两个线程不能及时的知道对方更新的数据,为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,这就引出了缓存一致性协议。
常见的协议有MSI,MESI,MOSI等。最常见的就是MESI协议。接下来给就以MESI协议来简单分析一下

什么是MESI

MESI表示缓存行的四种状态,分别是
1. M(Modify): 表示共享数据只缓存在当前CPU缓存中,并且被修改状态,也就是缓存的数据和主内存中的数据不一致
2. E(Exclusive): 表示缓存的独占状态,数据只缓存在当前CPU缓存中,并且没有被修改
**3. S(Shared) :**表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致
**4. I(Invalid) :**表示缓存已经失效

图解各个状态:
cache表示每个CPU里面的工作内存
share状态
a.CUP将内存中变量读取到工作内存,此时变量缓存行为S状态(共享状态)
在这里插入图片描述
b.CPU1更改了变量的值,此时CPU1变量缓存行表示为M状态(修改状态)
c.CPU1修改了变量值以后,会通知CPU2,将CPU2工作内存中的变量缓存行为I状态(失效状态)
在这里插入图片描述
d.假设CPU2中线程没有用到变量,只在CPU1线程中用到了变量,那么变量只在CPU1工作内存中存在,那么此时CPU1中变量缓存行为E状态(独占状态)

各个状态下的相互转化要理清
在这里插入图片描述

MESI带来的问题

举个例子,比如我们修改CPU0本地缓存中的一条信息,那么我们需要把当前状态更改为M状态,而且通知其他拥有该缓存数据的CPU1将缓存中数据置为I状态,并且等待确认返回。等待确认的过程会阻塞处理器,这会降低处理器的性能,我们将这个过程抽象为下图:
!](https://img-blog.csdnimg.cn/202005281833057.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1NTc2OTc2,size_16,color_FFFFFF,t_70)
虽然这个阻塞时间很短,应为这个等待远远比一个指令的执行时间长的多,所以针对MESI进行了优化,引入了Store Bufferes

Store Bufferes

Store Bufferes是一个写的缓冲,对于上述描述的情况,CPU0可以先把写入的操作先存储到StoreBufferes中,Store Bufferes中的指令再按照缓存一致性协议去发起其他CPU缓存行的失效。而同步来说CPU0可以不用等到Acknowledgement,继续往下执行其他指令,直到收到CPU0收到Acknowledgement再更新到缓存,再从缓存同步到主内存,图示如下:
store buffer模型

指令重排序

从上图我们可以看到,在store buffer收到其他CPU的ACK通知把变量更新到工作内存的时候,前后都会有指令在执行,倘若在之前用到了此变量的值,那么运行的结果就会和预期不一样,其实这就是指令重排序的根本原因,也就会产生所说的有序性问题
那么现在就能分析下面的代码产生不同结果的原因了吧

public class VolatileDemo02 {

    public static int x = 0, y = 0;

    public static void main(String[] args) throws InterruptedException {
        Map<String,Integer> map = new HashMap<>();
        for(int i=0;i<100000;i++){
            x=0;
            y=0;
            Thread one = new Thread(()->{
                int a = y;
                x = 1;
                map.put("a",a);
            });
            Thread two = new Thread(()->{
                int b = x;
                y = 1;
                map.put("b",b);
            });
            one.start();
            two.start();
            one.join();
            two.join();
            System.out.println("a=" + map.get("a")+", "+"b=" + map.get("b"));
        }
    }
}

打印结果按不同结果总结(可自行尝试),如下:

a=0, b=1
a=1, b=0
a=1, b=1
a=0, b=0
Process finished with exit code 0

怎么解决有序性问题呢

通过内存屏障防止指令重排序,解决有序性问题

相关链接
Java内存模型(JMM)
内存屏障

本文是综合自己的认识和参考各类资料(书本及网上资料)编写,若有侵权请联系作者,所有内容仅代表个人认知观点,如有错误,欢迎校正; 邮箱:1354518382@qq.com 博客地址:https://blog.csdn.net/qq_35576976/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值