并发编程(二)缓存一致性与JMM

#CPU高速缓存
线程是CPU调度的最小单元,线程涉及的目的最终仍然是更充分的利用计算机处理的效能,但是绝大部分的运算任务不能只依靠处理器“计算”就能完成,处
理器还需要与内存交互,比如读取运算数据、存储运算结果,这个I/O操作是很难消除的。
而由于计算机的存储设备与处理器的运算速度差距非常大,所以现代计算机系统都会增加一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存和处理器之间的缓冲:
将运算需要使用的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步到内存之中。
这里写图片描述
高速缓存从下到上越接近CPU速度越快,同时容量也越小。现在大部分的处理器都有二级或者三级缓存,从下到上依次为 L3 cache, L2 cache, L1 cache. 缓存又可以分为指令缓存和数据缓存,指令缓存用来缓存程序的代码,数据缓存用来缓存程序的数据
L1 Cache,一级缓存,本地core的缓存,分成32K的数据缓存L1d和32k指令缓存L1i,访问L1需要3cycles,耗时大约1ns;
L2 Cache,二级缓存,本地core的缓存,被设计为L1缓存与共享的L3缓存之间的缓冲,大小为256K,访问L2需要12cycles,耗时大约3ns;
L3 Cache,三级缓存,在同插槽的所有core共享L3缓存,分为多个2M的段,访问L3需要38cycles,耗时大约12ns;
#缓存一致性问题
CPU-0读取主存的数据,缓存到CPU-0的高速缓存中,CPU-1也做了同样的事情,而CPU-1把count的值修改成了2,
并且同步到CPU-1的高速缓存,但是这个修改以后的值并没有写入到主存中,CPU-0访问该字节,由于缓存没有更新,所以仍然是之前的值,就会导致数据不一致的问题
引发这个问题的原因是因为多核心CPU情况下存在指令并行执行,而各个CPU核心之间的数据不共享从而导致缓存一致性问题,为了解决这个问题,CPU生产厂商提供了相应的解决方案
总线锁
当一个CPU对其缓存中的数据进行操作的时候,往总线中发送一个Lock信号。其他处理器的请求将会被阻塞,那么该处理器可以独占共享内存。
总线锁相当于把CPU和内存之间的通信锁住了,所以这种方式会导致CPU的性能下降,所以P6系列以后的处理器,出现了另外一种方式,就是缓存锁。
缓存锁
如果缓存在处理器缓存行中的内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理不在总线上声明LOCK信号,而是修改内部的缓存地址,
然后通过缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域的数据,当其他处理器回写已经被锁定的缓存行的数据时会导致该缓存行无效。
所以如果声明了CPU的锁机制,会生成一个LOCK指令,会产生两个作用

  1. Lock前缀指令会引起处理器缓存回写到内存,在P6以后的处理器中,LOCK信号一般不锁总线,而是锁缓存
  2. 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
    #缓存一致性协议
    处理器上有一套完整的协议,来保证Cache的一致性,比较经典的应该就是MESI协议了,它的方法是在CPU缓存中保存一个标记位,这个标记为有四种状态
    Ø M(Modified) 修改缓存,当前CPU缓存已经被修改,表示已经和内存中的数据不一致了
    Ø I(Invalid) 失效缓存,说明CPU的缓存已经不能使用了
    Ø E(Exclusive) 独占缓存,当前cpu的缓存和内存中数据保持一直,而且其他处理器没有缓存该数据
    Ø S(Shared) 共享缓存,数据和内存中数据一致,并且该数据存在多个cpu缓存中
    每个Core的Cache控制器不仅知道自己的读写操作,也监听其它Cache的读写操作,嗅探(snooping)"协议
    单核Cache中每个Cache line有2个标志:dirty和valid标志,它们很好的描述了Cache和Memory(内存)之间的数据关系(数据是否有效,数据是否被修改),而在多核处理器中,多个核会共享一些数据,MESI协议就包含了描述共享的状态。
    在MESI协议中,每个Cache line有4个状态,可用2个bit表示,它们分别是:
    M(Modified)这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
    E(Exclusive)这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中。
    S(Shared)这行数据有效,数据和内存中的数据一致,数据存在于很多Cache中。
    I(Invalid)这行数据无效。
    M(Modified)和E(Exclusive)状态的Cache line,数据是独有的,不同点在于M状态的数据是dirty的(和内存的不一致),E状态的数据是clean的(和内存的一致)。
    S(Shared)状态的Cache line,数据和其他Core的Cache共享。只有clean的数据才能被多个Cache共享。
    I(Invalid)表示这个Cache line无效
    MESI状态之间的迁移过程如下:
    这里写图片描述
    这里写图片描述
    Intel的core i7处理器使用从MESI中演化出的MESIF协议,F(Forward)从Share中演化而来,一个Cache line如果是Forward状态,它可以把数据直接传给其它内核的Cache,而Share则不能
    #CPU 的优化执行
    除了增加高速缓存以为,为了更充分利用处理器内内部的运算单元,处理器可
    能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果
    充足,保证该结果与顺序执行的结果一直,但并不保证程序中各个语句计算的
    先后顺序与输入代码中的顺序一致,这个是
    处理器的优化执行
    ;还有一个就是
    编程语言的编译器也会有类似的优化,比如做指令重排来提升性能。
    #并发编程的问题
    其实原子性、可见性、有序性问题,是我们抽象出来的概念,他们的核心
    本质就是刚刚提到的缓存一致性问题、处理器优化问题导致的指令重排序问
    题。比如
    缓存一致性就导致可见性问题
    处理器的乱序执行会导致原子性问题指令重排会导致有序性问题。为了解决这些问题,所以在 JVM 中引入了 JMM 的概念
    #java内存模型
    内存模型定义了共享内存系统中多线程程序读写操作行为的
    规范
    ,来屏蔽各种
    硬件和操作系统的内存访问差异,来实现 Java 程序在各个平台下都能达到一致
    的内存访问效果。Java 内存模型的主要目标是定义程序中各个变量的
    访问规则
    ,也就是在虚拟机中将变量存储到内存以及从内存中取出变量(这里的变
    量,指的是共享变量,也就是实例对象、静态字段、数组对象等存储在堆内存
    中的变量。而对于局部变量这类的,属于线程私有,不会被共享)这类的底层
    细节。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。
    它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了 CPU
    多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下
    的可见性、原子性和有序性,。内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障
    Java 内存模型定义了线程和内存的交互方式,在 JMM 抽象模型中,分为主内存、工作内存。主内存是所有线程共享的,工作内存是每个线程独有的。线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,不能直接读写主内存中的变量。并且不同的线程之间无法访问对方工作内存中的变量,线程间的变量值的传递都需要通过主内存来完成,他们三者的交互关系如下:
    这里写图片描述
    总的来说,JMM 是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。
    #原子性
    即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
    在java中提供了两个高级的字节码指令monitorenter和monitorexit,在Java中对应的Synchronized来保证代码块内的操作是原子的
    #可见性
    指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
    Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。除了volatile,Java中的synchronized和final两个关键字也可以实现可见性
    #有序性
    即程序执行的顺序按照代码的先后顺序执行
    在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值