转载自  内存模型是怎么解决缓存一致性的

在再有人问你Java内存模型是什么,就把这篇文章发给他这篇文章中,我们介绍过关于Java内存模型的来龙去脉。

我们在文章中提到过,由于CPU和主存的处理速度上存在一定差别,为了匹配这种差距,提升计算机能力,人们在CPU和主存之间增加了多层高速缓存。每个CPU会有L1、L2甚至L3缓存,在多核计算机中会有多个CPU,那么就会存在多套缓存,那么这多套缓存之间的数据就可能出现不一致的现象。为了解决这个问题,有了内存模型。内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。

不知道小伙伴们有没有想过这样的问题:内存模型到底是怎么保证缓存一致性的呢?

接下来我们试着回答这个问题。首先,缓存一致性是由于引入缓存而导致的问题,所以,这是很多CPU厂商必须解决的问题。为了解决前面提到的缓存数据不一致的问题,人们提出过很多方案,通常来说有以下2种方案:

1、通过在总线加LOCK#锁的方式。

2、通过缓存一致性协议(Cache Coherence Protocol)。

在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。在总线上发出了LCOK#锁的信号,那么只有等待这段代码完全执行完毕之后,其他CPU才能从其内存读取变量,然后进行相应的操作。这样就解决了缓存不一致的问题。

但是由于在锁住总线期间,其他CPU无法访问内存,会导致效率低下。因此出现了第二种解决方案,通过缓存一致性协议来解决缓存一致性问题。

缓存一致性协议

缓存一致性协议(Cache Coherence Protocol),最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。

MESI的核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

在MESI协议中,每个缓存可能有有4个状态,它们分别是:

M(Modified):这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。

E(Exclusive):这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中。

S(Shared):这行数据有效,数据和内存中的数据一致,数据存在于很多Cache中。

I(Invalid):这行数据无效。

关于MESI的更多细节这里就不详细介绍了,读者只要知道,MESI是一种比较常用的缓存一致性协议,他可以用来解决缓存之间的数据一致性问题就可以了。

但是,值得注意的是,传统的MESI协议中有两个行为的执行成本比较大。

一个是将某个Cache Line标记为Invalid状态,另一个是当某Cache Line当前状态为Invalid时写入新的数据。所以CPU通过Store Buffer和Invalidate Queue组件来降低这类操作的延时。

如图:

当一个CPU进行写入时,首先会给其它CPU发送Invalid消息,然后把当前写入的数据写入到Store Buffer中。然后异步在某个时刻真正的写入到Cache中。

当前CPU核如果要读Cache中的数据,需要先扫描Store Buffer之后再读取Cache。

但是此时其它CPU核是看不到当前核的Store Buffer中的数据的,要等到Store Buffer中的数据被刷到了Cache之后才会触发失效操作。

而当一个CPU核收到Invalid消息时,会把消息写入自身的Invalidate Queue中,随后异步将其设为Invalid状态。

和Store Buffer不同的是,当前CPU核心使用Cache时并不扫描Invalidate Queue部分,所以可能会有极短时间的脏读问题

所以,为了解决缓存的一致性问题,比较典型的方案是MESI缓存一致性协议。

MESI协议,可以保证缓存的一致性,但是无法保证实时性。

内存模型

前面介绍过了缓存一致性模型,接着我们再来看一下内存模型。我们说过内存模型定义一系列规范,来保证多线程访问共享变量时的可见性、有序性和原子性。(更多内容请参考再有人问你Java内存模型是什么,就把这篇文章发给他。)

内存模型(Memory Model)如果扩展开来说的话,通常指的是内存一致性模型(Memory Sequential Consistency Model)

前面我们提到过缓存一致性,这里又要说内存一致性,不是故意要把读者搞蒙,而是希望通过对比让读者更加清楚。

缓存一致性(Cache Coherence),解决是多个缓存副本之间的数据的一致性问题。

内存一致性(Memory Consistency),保证的是多线程程序访问内存时可以读到什么值。

我们首先看以下程序:

初始:x=0 y=0Thread1:
S1:x=1
L1:r1=yThread2:
S2:y=2
L2:r2=x

其中,S1、S2、L1、L2是语句代号(S表示Store,L表示Load);r1和r2是两个寄存器。x和y是两个不同的内存变量。两个线程执行完之后,r1和r2可能是什么值?

注意到线程是并发、交替执行的,下面是可能的执行顺序和相应结果:

S1 L1 S2 L2 那么r1=0 r2=2S1 S2 L1 L2 那么r1=2 r2=1S2 L2 S1 L1 那么r1=2 r2=0

这些都是意料之内、情理之中的。但是在x86体系结构下,很可能得到r1=0 r2=0这样的结果。

如果没有Memory Consistency,程序员写的程序代码的输出结果是不确定的。

因此,Memory Consistency就是程序员(编程语言)、编译器、CPU间的一种协议。这个协议保证了程序访问内存时会得到什么值。

简单点说,内存一致性,就是保证并发场景下的程序运行结果和程序员预期是一样的(当然,要通过加锁等方式),包括的就是并发编程中的原子性、有序性和可见性。而缓存一致性说的就是并发编程中的可见性。

在很多内存模型的实现中,关于缓存一致性的保证都是通过硬件层面缓存一致性协议来保证的。需要注意的是,这里提到的内存模型,是计算机内存模型,而非Java内存模型。

总结

缓存一致性问题。硬件层面的问题,指的是由于多核计算机中有多套缓存,各个缓存之间的数据不一致性问题。

PS:这里还需要再重复一遍,Java多线程中,每个线程都有自己的工作内存,需要和主存进行交互。这里的工作内存和计算机硬件的缓存并不是一回事儿,只是可以相互类比。所以,并发编程的可见性问题,是因为各个线程之间的本地内存数据不一致导致的,和计算机缓存并无关系。

缓存一致性协议。用来解决缓存一致性问题的,常用的是MESI协议。

内存一致性模型。屏蔽计算机硬件问题,主要来解决并发编程中的原子性、有序性和一致性问题。

实现内存一致性模型的时候可能会用到缓存一致性模型。

思考

最后,再给大家留一道思考题:

既然在硬件层面,已经有了缓存一致性协议,可以保证缓存的一致性即并发编程中的可见性,那么为什么在写多线程的代码的时候,程序员要自己使用volatile、synchronized等关键字来保证可见性?

内存模型是怎么解决缓存一致性的相关推荐

  1. 白话Java基础—内存模型是怎么解决缓存一致性问题的?

    内存模型是怎么解决缓存一致性问题的? 本系列呢,主要将我理解的Java基础内容,以比较白话的方式,解释出来,希望能帮助大家快速的理解一些概念. 文章目录 内存模型是怎么解决缓存一致性问题的? 一.缓存 ...

  2. 面试必会系列 - 3.1 Redis知识点大汇总(数据类型,内存模型,持久化,缓存击穿,集群,一致性哈希等等)

    本文已收录至 Github(MD-Notes),若博客中图片模糊或打不开,可以来我的 Github 仓库,包含了完整图文:https://github.com/HanquanHq/MD-Notes,涵 ...

  3. Java内存模型深度解析:顺序一致性--转

    原文地址:http://www.codeceo.com/article/java-memory-3.html 数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数据 ...

  4. Java内存模型深度解析:顺序一致性

    码农网:http://www.codeceo.com/article/java-memory-3.html 数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数据竞 ...

  5. 顺序一致性内存模型与JMM的“顺序一致性”

    顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证.顺序一致性内存模型有两大特性. 1)一个线程中的所有操作必须按照程序的顺序来执行. 2)(不管程序是 ...

  6. java基础—java内存模型(JMM)CPU架构、缓存一致性、重排序、JMM的实现、JMM保证可见性、有序性问题的详解

    java基础-JMM(CPU架构.JMM保证可见性.有序性) 文章目录 java基础-JMM(CPU架构.JMM保证可见性.有序性) CPU架构 说一说现在计算机存储器的结构层次 使用CPU缓存,会导 ...

  7. 聊聊高并发(三十三)Java内存模型那些事(一)从一致性(Consistency)的角度理解Java内存模型

    可以说并发系统要解决的最核心问题之一就是一致性的问题,关于一致性的研究已经有几十年了,有大量的理论,算法支持.这篇说说一致性这个主题一些经常提到的概念,理清Java内存模型在其中的位置. 一致性问题更 ...

  8. 关于缓存一致性协议、MESI、StoreBuffer、InvalidateQueue、内存屏障、Lock指令和JMM的那点事

    前言 事情是这样的,一位读者看了我的一篇文章,不认同我文章里面的观点,于是有了下面的交流. 可能是我发的那个狗头的表情,让这位读者认为我不尊重他.于是,这位读者一气之下把我删掉了,在删好友之前,还叫我 ...

  9. 缓存一致性MESI与内存屏障

    文章目录 1. 高速缓存 1.1. 什么是高速缓存Cache 1.2. 缓存行 2. 伪共享问题 2.1. 什么是伪共享 2.2. 解决伪共享 3. 总线锁 4. 缓存锁 5. 缓存一致性协议 5.1 ...

最新文章

  1. 利用java做一个简单的计算器
  2. 塞尔达传说gba_回顾 | 猹鱼主题速写:塞尔达传说 英国绅士
  3. 【调参实战】那些优化方法的性能究竟如何,各自的参数应该如何选择?
  4. 简单总结过去的2021年
  5. php html转为pdf文件,php使用wkhtmltopdf实现html转pdf
  6. 创建MySql用户并给用户授权,设置mariadb默认编码,修改mariadb数据存储目录datadir...
  7. 管道(Pipe)/createPipe
  8. LintCode_44 最小子数组
  9. 抖音矩阵号系统搭建及开发思路分享丨抖音短视频关键词优化
  10. 计算机毕业论文乐谱播放器,单片机音乐播放器毕业论文
  11. 电脑截图怎么转换成文字?学会这个方法,轻松实现
  12. windows 7 64位 刷J-Link固件 无法识别成串口时 安装 sam-ba at91 usb to serial converter 驱动不成功的解决办法
  13. php如何批量发送短信,如何在php中运行批量短信api [关闭]
  14. 「PKUSC2018」最大前缀和 LOJ#6433BZOJ5369
  15. 基于异质图神经网络的未知恶意程序检测
  16. 工作,究竟意味着什么
  17. Barefoot Networks在其下一代网络中实现无以伦比的可视化和高效化
  18. SpringBoot系列之(三):启动过程
  19. docker logs命令
  20. Linux进程调度与性能优化 | 真货

热门文章

  1. fc协议有哪些数据传输服务器,FC协议详解
  2. 布斯乘法以及带符号数的运算
  3. C++实现线段树求区间和-区间查询
  4. 数据结构与算法--B树原理及实现
  5. autohotkey php,Autohotkey+php实现免浏览器听录音
  6. java while等待 yeild_Java中run(), start(), join(), wait(), yield(), sleep()的使用
  7. Java基础知识——Sting、StringBuild、StringBuffer的区别
  8. Python import以及os模块
  9. E:Tree Queries(假树链剖分写法)
  10. P3357 最长k可重线段集问题(网络流/串联/拆点)