MESI与Volatile(二)

2024-02-13 10:38
文章标签 mesi volatile

本文主要是介绍MESI与Volatile(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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步了,所以造成了数据丢失。

这篇关于MESI与Volatile(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/705307

相关文章

关键字synchronized、volatile的比较

关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字的执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。多线程访问volatile不会发生阻塞,而synchronize

关键字volatile有什么含意?

1. 并行设备的硬件寄存器。存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改。当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地 址的数据进行假设,也就是要去相应的内存地址里取。 2. 一个中断服务程序中修改的供其他程序检测的变量。volatile提醒编译器,它后面所定义的变量随时都有可能改变。因此编译后的程序每次需要存储或读取这

java内存模型及volatile关键字

文章目录 一、基本概念二、java内存模型三、volatile关键字 一、基本概念 在并发编程过程中,我们经常会遇到三类问题:原子性问题,可见性问题,有序性问题。下面我们来介绍一下和这些问题相关的三个概念。 1.原子性 也就是执行一个操作,要不全部执行成功,要不执行失败。比如a=0,这个操作就是原子性的,要么赋值成功,要么赋值失败。再比如a++操作,这个操作就不是原子性的,它是三

Java并发编程--深入理解volatile关键字

前言 一个月以前就准备写篇关于volatile关键字的博客,一直没有动笔,期间看了大量的文章,发现一个小小volatile关键字竟然涉及JMM(Java memory model),JVM(Java virtual machine),Java多线程同步与安全各个方面的知识,写起了非常的困难,后面附带的参考文献仅仅是我看过文献的一部分。 Java memory model(Java内存模型)

java volatile变量及其使用场景

java中的一种稍弱的同步机制,就是volatile变量,用于确保将变量的更新操作通知到其他线程。 变量声明为volatile后: (1)编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序(重排序不懂的,可以自行百度,需要理解)。 (2)volatile变量不会被缓存在寄存器或对其他处理器不可见的地方 因此volatile变量总是会返回最新的值。

【编程底层思考】Java中volatile关键字的使用和作用

在编程中,volatile 关键字是一个类型限定符,它用来告诉编译器,即使这个变量被频繁地访问,也不应该优化掉对它的读写操作。volatile 通常用于多线程编程中,特别是在嵌入式系统或硬件接口编程中,用来确保对某些变量的访问是按照代码的顺序执行的,而不是被编译器重排序。 以下是 volatile 关键字的一些使用场景和作用: 防止编译器优化:编译器在编译代码时,可能会对代码进行优化,以提高程序

J.U.C Review - volatile / synchronized / 锁 深入剖析

文章目录 几个基本概念内存可见性重排序happens-before规则 volatile的内存语义内存可见性禁止重排序内存屏障 volatile的用途总结 synchronized与锁Synchronized关键字Java对象头无锁、偏向锁、轻量级锁和重量级锁偏向锁实现原理撤销偏向锁 轻量级锁重量级锁 锁的升级流程各种锁的优缺点对比 几个基本概念 内存可见性 在Jav

Redis的内存淘汰策略- volatile-lru

`volatile-lru` 策略简介 在 `volatile-lru` 策略下,当 Redis 的内存使用达到配置的上限(`maxmemory`)时,它会优先删除那些设置了过期时间的键,并且选择最近最少使用的键进行删除。LRU 算法的核心思想是,优先删除那些最近没有被访问的数据,以腾出内存空间给新数据或更常用的数据。 这种策略适用于以下场景: - 需要优先删除临时数据的场景。 - 应用中

线程安全关键字synchronized和volatile

Java内存模型(JMM) 提到这两个有关于线程的关键字,那么我们不得不提到Java的内存模型了(JMM),下面我们先看一下Java内存模型在处理多线程方面的工作原理图。 Java内存模型(java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。 工作内存是线程私有的,各个线程之间不共享

多线程——volatile和static

如果变量用 public static修饰,那么这个变量会被放在JVM的方法区。如果是在多线程的环境下,那么这个变量应该是多个线程均可见的。在JVM被设置为-server模式时,JVM为了提升线程运行的效率,一直在私有堆栈中取值,这会导致不同步。解决办法就是使用volatile关键字修饰变量,强制JVM从公共内存取值。