【线程、锁】 MESI、JMM、volatile之间的关系

1. 前言

看的知识多了,有点混淆,特此把相关的概念梳理一下,做个区分

2. 结论

结论:MESI与JMM无关,更与volatile无关,volatile只是实现JMM的一部分

JMM是一种虚的概念,目的是实现java跨平台。其中,volatile可以解决JMM中存在的一些问题,但不能解决所有问题,比如不能解决原子性。但是可以解决重排序和可见性。

MESI是硬件层面的,为了解决cpu与缓存之间的数据一致性问题。

3. java内存模型

java内存模型,英文全称为java memory model,简称JMM。Java内存模型是一种抽象的概念,并不真实存在,内存模型描述了java多线程与共享内存的交互过程。更进一步说,java内存模型描述了共享内存中的值被多线程修改和读取的过程,规定了共享变量的可见性和执行顺序。

JMM概念中存在工作内存(线程私有)和主内存(共享内存)。

Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行。

java虚拟机规范中定义了Java内存模型(java Memory Model,JMM),用来屏蔽掉各种硬件和操作系统访问内存的差异(内存访问差异),来实现java程序在各种平台下都能达到一致的并发效果,一次编写,到处运行。

3.1 JMM的三个特征

Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的,那我们依次看一下这三个特征:

  • 原子性(Atomicity)
    一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。

    基本类型数据的访问大都是原子操作,long 和double类型的变量是64位,但是在32位JVM中,32位的JVM会将64位数据的读写操作分为2次32位的读写操作来进行,这就导致了long、double类型的变量在32位虚拟机中是非原子操作,数据有可能会被破坏,也就意味着多个线程在并发访问的时候是线程非安全的

    存在线程非安全问题,只能通过加锁的形式解决。volatile解决不了原子性。

  • 可见性:
    一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量的这种修改(变化)。

    Java内存模型是通过将在工作内存中的变量修改后的值同步到主内存,在读取变量前从主内存刷新最新值到工作内存中,这种依赖主内存的方式来实现可见性的。由于这种方式同步,导致可能延迟的情况。

    无论是普通变量还是volatile变量都是如此,区别在于:volatile的特殊规则保证了volatile变量值修改后的新值立刻同步到主内存,每次使用volatile变量前立即从主内存中刷新,因此volatile保证了多线程之间的操作变量的可见性,而普通变量则不能保证这一点。

    volatile可以解决可见性

    除了volatile关键字能实现可见性之外,还有synchronized,Lock,final也是可以的

  • 有序性:
    对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。

    volatile可以解决有序性,会强制避免指令重排

4. MESI

MESI是硬件层面的知识。

线程是CPU调度的最小单元,线程设计的目的最终仍然是更充分的利用计算机处理的效能,但是绝大部分的运算任务不能只依靠处理器“计算”就能完成,处理器还需要与内存交互,比如读取运算数据、存储运算结果,这个 I/O 操作是很难消除的。而由于计算机的存储设备与处理器的运算速度差距非常大,所以现代计算机系统都会增加一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存和处理器之间的缓冲:将运算需要使用的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步到内存之中。

查看我们个人电脑的配置可以看到,CPU有L1,L2,L3三级缓存,大致粗略的结构如下图所示:
在这里插入图片描述
从上图可以知道,L1和L2缓存为各个CPU独有,而有了高速缓存的存在以后,每个 CPU 的处理过程是,先将计算需要用到的数据缓存在 CPU 高速缓存中,在 CPU进行计算时,直接从高速缓存中读取数据并且在计算完成之后写入到缓存中。在整个运算过程完成后,再把缓存中的数据同步到主内存。
由于在多 CPU 中,每个线程可能会运行在不同的 CPU 内,并且每个线程拥有自己的高速缓存。同一份数据可能会被缓存到多个 CPU 中,如果在不同 CPU 中运行的不同线程看到同一份内存的缓存值不一样就会存在缓存不一致的问题,那么怎么解决缓存一致性问题呢

CPU层面提供了两种解决方法:总线锁和缓存锁

  • 总线锁
    总线锁,简单来说就是,在多CPU下,当其中一个处理器要对共享内存进行操作的时候,在总线上发出一个 LOCK#信号,这个信号使得其他处理器无法通过总线来访问到共享内存中的数据,总线锁定把 CPU 和内存之间的通信锁住了(CPU和内存之间通过总线进行通讯),这使得锁定期间,其他处理器不能操作其他内存地址的数据。然而这种做法的代价显然太大,那么如何优化呢?优化的办法就是降低锁的粒度,所以CPU就引入了缓存锁。

    该方案效率太低了

  • 缓存锁
    缓存锁的核心机制是基于缓存一致性协议来实现的,一个处理器的缓存回写到内存会导致其他处理器的缓存无效,IA-32处理器和Intel 64处理器使用MESI实现缓存一致性协议(注意,缓存一致性协议不仅仅是通过MESI实现的,不同处理器实现了不同的缓存一致性协议)

    该方案效率高!MESI是该方案的有力支持。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值