MESI与Volatile(二)

el/2024/7/17 3:01:07

MESI缓存一致性协议

定义:

多核CPU的情况下有多个一级缓存一致性协议MESI保证缓存内部数据的一致,不让系统数据混乱。

缓存行(Cache line):缓存存储数据的单元

缓存行状态:

状态描述监听任务
M 修改 (Modified)该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。缓存行必须时刻监听所有试图读该缓存行相对于主存的操作。这种操作必须在该缓存行数据写回主存将状态变成S(共享)状态之后执行。
E 独享、互斥 (Exclusive)该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。缓存行也必须监听其它缓存读该缓存行相对于主存的操作。一旦有这种操作,该缓存行需要变成S(共享)状态。
S 共享 (Shared)该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。
I 无效 (Invalid)该Cache line无效。

注意:

对于M和E状态而言总是精确的,他们在和该缓存行的真正状态是一致的,而S状态可能是非一致的

一个缓存行不能确保其他缓存行的数据都已作废,因为其它缓存不会广播他们作废掉该缓存行的通知,所以不会从 S 转变为 E

一个Cache line所处的状态 和 另外一个cache line 需要调整的状态

MESI
M×××
E×××
S××
I

举个栗子来说:

假设Cache 1 中有一个变量x = 0的Cache line 处于S状态(共享)

那么其他拥有x变量的Cache 2、Cache 3等x的Cache line调整为S状态(共享)或者调整为 I 状态(无效),它们不能调整为M 或 E

volatile关键字

定义:

volatile是JVM提供的轻量级的同步机制。volatile关键字有如下两个作用:

  • 保证被volatile修饰的共享变量对所有线程总是可见的
  • 禁止指令重排序优化

volatile可见性

volatile关键字的作用是保证可见性,通过在指令前添加汇编前缀lock将当前处理器缓存行的数据写回到系统内存,并触发硬件缓存锁定机制

硬件缓存锁定机制

  • 触发总线锁 —— Cache line容量不足【效率低】
  • 触发MESI缓存一致性协议

volatile有序性

​ volatile关键字另一个作用就是**禁止指令重排优化,**从而避免多线程环境下程序出现乱序执行的现象,volatile是通过 内存屏障 实现禁止指令重排优化

内存屏障

定义:

内存屏障,又称内存栅栏,是一个CPU指令。作用有两个

  • 保证特定操作的执行顺序
  • 保证某些变量的内存可见性

JVM中提供了四类内存屏障指令:

屏障类型指令示例说明
LoadLoadLoad1; LoadLoad; Load2保证load1的读取操作在load2及后续读取操作之前执行
StoreStoreStore1; StoreStore; Store2在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStoreLoad1; LoadStore; Store2在stroe2及其后的写操作执行前,保证load1的读操作已读取结束
StoreLoadStore1; StoreLoad; Load2保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行

作用:

在指令间插入一条Memory Barrier

  • 告诉编译器和CPU,任何指令都不能和这条Memory Barrier指令重排序
  • 强制刷出CPU的各种缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本

指令重排序导致问题 —— DCL例子

public class DoubleCheckLock {private volatile static DoubleCheckLock instance;private DoubleCheckLock(){}public static DoubleCheckLock getInstance(){//第一次检测if (instance==null){//同步synchronized (DoubleCheckLock.class){if (instance == null){//多线程环境下可能会出现问题的地方instance = new  DoubleCheckLock();}}}return instance;}
}
// 对象实例化的三个步骤
memory = allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance = memory;//3.设置instance指向刚分配的内存地址,此时instance!=null/* 重排 */memory=allocate();//1.分配对象内存空间
instance=memory;//3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory);//2.初始化对象/** 
解释:
由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的!当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题
*/

volatile内存语义实现

对于加了 volatile 的共享变量,JMM针对编译器制定 volatile 重排序的规则

第一个操作第二个操作:普通读写第二个操作:volatile读第二个操作:volatile写
普通读写可以重排可以重排不可以重排
volatile读不可以重排不可以重排不可以重排
volatile写可以重排不可以重排不可以重排

JMM规定:

  • volatile写 不可以重排在 任何操作 之前
  • volatile读 不可以重排在 任何操作 之后
  • volatile读 不可以重排在 volatile写 之前

例子

class VolatileBarrierExample {int a;volatile int v1 = 1;volatile int v2 = 2;void readAndWrite() {int i = v1;      // 第一个volatile读int j = v2;       // 第二个volatile读a = i + j;         // 普通写v1 = i + 1;       // 第一个volatile写v2 = j * 2;       // 第二个volatile写}
}

volatile不保证原子性

自增操作例子

自增操作在底层执行的操作不是原子的,分为:

  1. Load 数据
  2. Increment
  3. Store 刷回主存

100自增

​ 当线程A执行到第2步后【Increment -> 101】,线程B执行完第3步操作【Store -> 101】并通知线程A缓存失效,因为线程A已经执行完第1步和第2步了,所以造成了数据丢失。


http://www.ngui.cc/el/4893708.html

相关文章

synchronized锁机制(三)

Java锁机制(一) 定义: ​ 锁的设计是为了解决线程并发安全问题,Java提供了两种锁来实现同步互斥访问 synchronized 和 lock Monitor监视器锁 定义: ​ 任何一个对象都有一个Monitor与之关联,通过成对的…

AQS框架详解(四)

AQS概述 定义 AbstractQueuedSynchronizer 抽象队列同步器是一个框架,它维护两种队列,基于一个状态量 state 来实现同步器,实现该框架时,仅仅需要实现几种方法就可以自定义同步器 两种队列 同步等待队列 CLH CLH队列是一种基于…

AQS框架应用(五)

AQS框架应用(五) 定义: ​ 基于AQS框架,Doug Lea给出了很多内置的实现类 实现类: 可以看出,有多个工具类是基于AQS框架实现的 独占式同步信号量 ReentrantLock Semaphore 共享式同步信号量 CountDown…

类加载机制(一)

类加载 定义: ​ 当 用到某个类 时,首先需要通过 类加载器 把类加载到JVM上 组成: 引导类加载器 —— 加载lib目录扩展类加载器 —— 加载ext目录应用程序类加载器 —— 加载ClassPath目录 【用户写的】自定义加载器 —— 加载用户自定义…

JVM内存模型(二)

JVM内存模型 定义: ​ JVM内存模型,又称为运行时数据区。【区别Java内存模型】 紫色 —— 线程私有 浅黄色 —— 线程共享 虚拟机栈 定义: ​ 虚拟机栈是一个线程私有的、FILO的结构。 ​ 一个线程到来时,从虚拟机栈中&…

JVM内存分配机制详解(三)

JVM对象创建过程 1.类加载检查 流程: JVM在遇到一条new指令检查这个指令的参数是否在常量池中定位到一个类的符号引用 【方法区】并检查 符号引用 所代表的类是否被类加载器加载 包括 new关键字、对象克隆、对象序列化等等 **总的来说:**执行一个类…

Spring IOC (三)

Spring IOC (三) refresh() 后八个方法 refresh() 定义: ​ 这个方法中,内置了13个方法,做了很多事情 方法: 方法作用prepareRefresh()保存了容器的启动时间,启动标志等obtainFreshBeanFac…

Spring循环依赖(四)

循环依赖 什么是循环依赖? 依赖:在Spring中,A类将其他的类当作属性时,就造成了A依赖其他类 定义: ​ 现有A、B两个类,如果A类将B类作为属性(并设置Autowired),B类也将…

想要学习Python?8年老Python程序员的自学书单曝光,入门必看

什么是Python? 它是一种类似C语言的人工智能便捷语言。它简单易学、并且开源免费,应用面广,所以,是自学门槛相对低的一种计算机语言,能为你的生活、工作提升效率。 为什么选择学习python的人越来越多? 全…

Python基础学习:都给我学起来,这样快速处理CSV、JSON和XML数据的方法太简便了

Python的卓越灵活性和易用性使其成为最受欢迎的编程语言之一,尤其是对于数据处理和机器学习方面来说,其强大的数据处理库和算法库使得python成为入门数据科学的首选语言。在日常使用中,CSV,JSON和XML三种数据格式占据主导地位。下…