JMM应对内存系重排序

简介: CPU提供内存屏障指令,来解决内存系重排序。读屏障清空本地的invalidate queue保证之前的所有load都已经生效;写屏障清空本地的store buffer,使得之前的所有store操作生效
我是石页兄,朋友不因远而疏,高山不隔友谊情;偶遇美羊羊,我们互相鼓励

欢迎关注微信公众号「架构染色」交流和学习

一、背景

为了性能,编译时和运行时都会有重排序,造成指令执行顺序变了,宏观上从这3点了解重排序:

  1. 线程内有序:如果再本线程内观察,所有的操作都是有序的,即线程内表现为串行的语义(Within Thread As-If-Serial Semantics)。
  2. 线程间无序:如果再一个线程中观察另一个线程,所有的操作都是无序的,即 指指令重排序现象和工作内存与主内存同步延迟现象。
  3. 总会有重排序:指令重排序在任何时候都有可能发生,与是否为多线程无关,之所以在单线程下感觉没有发生重排序,是因为线程内表现为串行的语义的存在。

从编译运行视角分为两类:

  1. 编译期重排序:包括 编译器优化的重排序。
  2. 运行期重排序:包括 指令级并行的重排序,内存系统的重排序。

二、内存系统的重排序

2.1 StoreBuffer

什么是StoreBuffer,已变更的数据立即写到内存太慢,所以先写到Store Buffer。 举个例子描述一下,饭店的厨师饭做好了,厨师为了提升做菜的效率,厨师不会自己做好后直接端给你,而是放到传菜间由服务员端给你;但无论服务员什么时候端给你,都不算是做好了直接给你吃,而是delay了一会儿。

image.png

  • 对Cache的写入暂时来不及处理,可以先写到Buffer,后续再处理
  • Store不用等待写Cache以及维护缓存一致的延迟;也可对重叠的Store进行合并
  • 读Cache时需要读StoreBuffer,避免自己的Store读不到
  1. 优点:

    • 可以保证core内的指令流水线持续运行,
    • 它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。
    • 通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,可以减少对内存总线的占用。
  2. 缺点:

    • 每个处理器上的写缓冲区,仅对它所在的处理器可见。这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的写操作的执行顺序,不一定与内存实际发生的写操作顺序一致!

2.2 Invalidate Queue

什么是Invalidate Queue?举例解释一下:其它CPU通知说,我缓存的数据无效了,但是我在忙别的,不想打断正在做的事情,于是提供了一个通知队列,让其它把CPU把缓存无效的通知先放到通知队列中,等我忙完了再去处理

image.png

  • 本地CPU来不及处理别的CPU发来的Invalidate信息时,可以在本地做一下Buff,后续处理
  • 带来的问题:远程CPU写入成功,但本地CPU还是会读到旧值
  1. 优点:

    • 正在处理的事情不中断
  2. 缺点:

    • 处理器对内存的读操作的执行顺序,不一定与内存实际发生的写操作顺序一致!使用已经过期的数据,而不是最新的。

在两个CPU同时运行的情况下,CPU0自身视角来说,没有重排发生,一切都那么自然,但是CPU1却看到CPU0发生了重排(reordering memory)。这就是内存系统重排序。

三、禁止内存系统的重排序

3.1 读写屏障

store buffer 和  Invalidate Queue 带来的乱序如何解决?

CPU通常提供了内存屏障指令,来解决这样的乱序问题。读屏障,清空本地的invalidate queue,保证之前的所有load都已经生效;写屏障,清空本地的store buffer,使得之前的所有store操作都生效。通俗来说就是两点:

  • 写屏障:保证把更新写到内存
  • 读屏障:保证从内存读取最新数据

JMM把内存屏障分为四类,其实就是读、写屏障的组合:

image.png

JMM 的处理器重排序规则会要求 java 编译器在生成指令序列时,插入特定类型的内存屏障指令,来禁止特定类型的处理器重排序(不是所有的处理器重排序都要禁止)。

3.2 java中禁止重排序的操作

  • volatile关键字
  • unsafe 的内存屏障方法
  • synchronized锁

四、最后说一句

我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。

欢迎点击链接扫马儿关注、交流。

相关文章
|
1月前
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【9月更文挑战第37天】在Java的世界里,内存模型是隐藏在代码背后的守护者,它默默地协调着多线程环境下的数据一致性和可见性问题。本文将揭开Java内存模型的神秘面纱,带领读者探索其对并发编程实践的深远影响。通过深入浅出的方式,我们将了解内存模型的基本概念、工作原理以及如何在实际开发中正确应用这些知识,确保程序的正确性和高效性。
|
13天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
3月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
70 0
|
12天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
33 2
|
3月前
|
存储 缓存 Java
Java内存模型(JMM)
Java内存模型(JMM)是一个抽象概念,用于规范程序中各种变量(实例字段、静态字段及数组元素)的访问方式,确保不同Java虚拟机(JVM)上的并发程序结果一致可靠。JMM定义了主存储器(所有线程共享)与工作存储器(线程私有)的概念,线程间通过主存储器进行通信。JMM具备三大特性:原子性(确保基本读写操作的不可分割)、可见性(确保一个线程对共享变量的修改对其他线程可见)、有序性(防止指令被处理器或编译器重排序影响程序逻辑)。通过这些特性,JMM解决了多线程环境下的数据一致性问题。
|
3月前
|
安全 Java 程序员
深入浅出Java内存模型:探索JMM的奥秘
在Java编程世界中,理解其内存模型(JMM)是提升代码性能和确保线程安全的关键。本文将带你走进Java内存模型的大门,通过浅显易懂的方式揭示其工作原理,并指导你如何在实际开发中有效利用JMM来避免常见的并发问题。
|
4月前
|
存储 安全 Java
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
109 13
|
4月前
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其在并发编程中的应用
【7月更文挑战第8天】本文旨在探索Java内存模型(JMM)的奥秘,揭示它在并发编程中的关键作用。通过深入浅出的方式,我们将了解JMM的基本概念、关键特性,以及它如何影响多线程程序的行为。文章将带领读者从理论到实践,探讨JMM对编写高效、可靠并发应用的重要性,并展示如何利用这些知识解决实际问题。
70 7
|
4月前
|
Java 程序员 编译器
Java面试题:解释Java内存模型(JMM)是什么,它为何重要?
Java面试题:解释Java内存模型(JMM)是什么,它为何重要?
71 2
|
4月前
|
存储 安全 算法
深入理解Java内存模型(JMM)
在Java的并发编程领域,内存模型是一个不可忽视的核心概念。它定义了多线程环境下变量的访问规则,影响着程序的正确性和性能。本文将探讨Java内存模型(JMM)的基本结构、工作原理及其对编写高效、线程安全代码的重要性。