Java 内存模型之 JMM

news2024/12/23 5:29:56

—— 计算机存储结构

  • 计算机存储结构,从本地磁盘到主存到 CPU 缓存,也就是硬盘到内存,到CPU,一般对应的程序的操作就是从数据库到内存然后到CPU进行计算
  • CPU拥有多级缓存,(CPU和物理主内存的速度不一致,远高于主内存),CPU的运行并不是直接操作内存,而是先把内存里面的数据读到缓存,而内存的读写操作就会造成不一致的问题
  • JVM 规范中试图定义一种 Java 内存模型(Java Memory Model,简称 JMM)来屏蔽各种硬件和操作系统的内存访问差异,实现让 Java 程序在各种平台下都能达到一致的内存访问效果
    在这里插入图片描述

—— JMM 概念

  • 定义:JMM (Java 内存模型,Java Memory Model)本身是一种抽象的概念,并不真实存在,它仅仅描述的是一组规范或约定,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式,并决定一个线程对共享变量的写入同时如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性、有序性展开
  • 原则:JMM 的关键技术点都是围绕多线程的原子性、可见性、有序性展开
  • 作用:
    • 通过 JMM 来实现线程和主内存之间的抽象关系
    • 屏蔽掉各个硬件平台和操作系统的内存访问差异,实现让 Java 程序在各种平台下都能达到一致的内存访问效果

多线程对变量的读写过程

  • 定义的所有共享变量都存储在物理主内存
  • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量副本
  • 线程对共享变量所有的操作都必须先在线程自己的工作内存进行后写回主内存,不能直接从主内存中进行读写(不能越级
  • 不同线程之间无法访问其他线程工作内存中的变量,线程变量值间的传递需要通过主内存来进行(同级之间不能相互访问

—— 三大特性

可见性

  • 当一个线程修改了某个共享变量的值,其他线程能够立即知道改变更,JMM 规定了所有的变量都存储在主内存
    在这里插入图片描述
  • 系统主内存共享变量数据修改被写入的时机是不确定的,多线程并发下很可能出现脏读,所以每个线程都有自己的工作内存,线程自己的工作内存中,保存了该线程使用的的主内存副本拷贝,线程对变量的所有操作都必须在线程自己的工作内存中进行,而不能够直接读写主内存的变量。不同线程之间也无法访问对方工作内存中的变量,线程间变量值的传递都需要通过主内存来访问

原子性

  • 操作不可再分,不可被打断;即多线程环境下,操作不能被其他线程所干扰

有序性

  • 对于一个线程的执行代码而言,我们习惯性的认为代码总是从上而下,有序执行。但为了提升性能,编译器和处理器通常会对指令序列进行重新排序。Java 规范规定 JVM 线程内部维持顺序话语义,即只要程序的最终结果与它顺序化执行的结果一致,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序
  • 优点:JVM 能根据处理器特性(CPU 的多级缓存系统、多核处理器)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能
  • 缺点:指令重排可以保证串行语义的一致性,但没有义务保证多线程的语义一致(即可能产生脏读),简单说,两行以上不相干的代码在执行的时候可能先执行的不是第一条,不见的是从上到下顺序执行,执行顺序会被优化
  • 源码到最终执行顺序:源代码——》编译器优化的重排——》指令并行的重排——》内存系统的重排——》最终执行的命令

重排序

  • 单线程环境里确保程序最终结果和代码顺序执行的结果一致
  • 处理器在进行重排序时必须考虑指令之间的数据依赖性(若两个操作访问同一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性)
  • 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测

在这里插入图片描述

——happens-before

在 JMM 中,如果一个操作执行结果需要对另一个操作可见性,或者代码重排序,那么这两个操作之间必须存在 happens-before(先行发生)原则,逻辑上的先后关系

总原则

  • 如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
  • 两个操作之间存在 happens-before 关系,并不意味着一定按照 happens-before 原则制定的顺序来执行。如果重排序之后的执行结果与按照 happens-before 关系来执行的结果一致,那么这种重排序并不非法

八条规则

  1. 次序规则:一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作
  2. 锁定规则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作
  3. volatile 变量规则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读可见
  4. 传递规则:如果操作 A 先行发生于 B 操作,而操作 B 又先行发生于 C 操作,则 A 操作 先行发生于 C 操作
  5. 线程启动规则:Thread 对象的 start 方法先行发生于此线程的每一个动作
  6. 线程中断规则:对线程 interrupt 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 Thread.interrupt() 检测到是否发生中断
  7. 线程终止规则:线程中所有操作都先行发生于对此线程的终止检测, 我们可以通过 isAlive 方法等手段检测线程是否已经终止执行
  8. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始(对象没有完成初始化之前是不能调用 finalized 方法)

本质

  • 在 Java 语言里, Happens-Before 语义本质上是一种可见性;A Happens-Before B 意味着 A 发生过的事情对 B 来说是可见的,无论 A 事件 和 B 事件是否发生在同一个线程里
  • JMM 的设计分为两部分:
    • 一部分是面向程序员,也就是 Happens-Before 规则,通俗易懂阐述了强内存模型,只要 理解 Happens-Before 规则,就能编写并发安全程序
    • 另一部分是针对 JVM 实现,为了尽可能少的对编译器和处理器做约束,从而提高性能,JMM 在不影响程序执行结果的前提下对其不做要求,允许重排序

——volatile

特点

  • 不保证原子性:多线程环境下,“数据计算”和“数据赋值”操作可能多次出现,若数据加载之后,若主内存 volatile 修饰变量发生修改之后,线程工作内存中的操作将会作废,去读主内存的最新值,操作出现写丢失问题。即各线程私有内存和主内存公共内存变量不同步,进而导致数据不一致,对于多线程修改主内存共享变量的场景必须使用加锁同步,volatile 变量不适合参与到依赖当前值的计算,通常 volatile 用作保存某个状态的 boolean 值或 int 值(比如 num++,为 3 个指令,非原子操作)
  • 可见性:保证不同线程对某个变量完成操作后结果及时可见,即该共享变量一旦改变,所有线程立即可见,读到该变量的最新值;当某个线程收到通知,去读取 volatile 变量值时,线程私有工作内存的数据失效,需要重新回到主内存中读取最新的值
  • 有序性(禁止指令重排序)

内存屏障(Memory Barrier) 保证可见性和有序性(禁重排)

  • 对于编译器的重排序,JMM 会根据重排序的规则,禁止特定类型的编译器重排序
  • 对于处理器的重排序,Java 编译器在生成指令序列的适当位置,插入内存屏障指令,来禁止特定类型的处理器排序

内存语义

  • 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值立即刷新回主内存中
  • 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量

内存屏障

  • 定义:也称为内存栅栏、屏障指令等,是一类同步屏障指令,是 CPU 或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,避免代码重排序。内存屏障实际是一种 JVM 指令,Java 内存模型的重排规则会要求 Java 编译器在生成 JVM 指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile 实现了Java 内存模型中可见性和有序性(禁重排),但 volatile 无法保证原子性
  • 内存屏障之前的所有写操作都要回写到主内存
  • 内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现可见性)
  • 重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。即对一个 volatile 变量的写,先行发生于任意后续对这个 volatile 变量的读,也叫写后读

屏障分类

  • 写屏障(Store Memory Barrier): 在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中,告诉处理器在写屏障之前将所有存储在缓存中的数据同步到主内存
    在这里插入图片描述

  • 读屏障(Load Memory Barrier):在读指令之前插入读屏障,让工作内存或 CPU 高速缓存中的缓存数据失效,重新回到主内存中获取最新数据;处理器在读屏障之后的读操作,都是在读屏障之后执行,保证 Load 屏障指令之后的读取数据指令一定能够读到最新数据
    在这里插入图片描述

源码分析:Unsafe.class(loadFence、storeFence、fullFence)
在这里插入图片描述

Happens-Before 之 volatile 变量规则

  • 当第一个操作为 volatile 读时,不论第二个操作是什么,都不能重排序。这个操作保证了 volatile 读之后的操作不会被重排到 volatile 读之前
  • 当第二个操作为 volatile 写时,不论第一个操作是什么,都不能重排序
  • 当第一个操作为 volatile 写时,第二个操作为 volatile 读时,不能重排

volatile 变量的读写过程

  • Java 内存模型中定义的 8 种每个线程自己的工作内存与主物理内存之间的原字操作:
    read(读取)——》load(加载)——》use(使用)——》assign(赋值)——》store(存储)——》write(写入)——》lock(锁定)——》unlock(解锁)

在这里插入图片描述

  • read:作用于主内存,将变量的值从主内存传输到工作内存中
  • load:作用于主内存,将read 从主内存传输的变量值放入工作内存变量副本中,数据加载
  • user:作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当 JVM 遇到该变量的字节指令时会执行该操作
  • assign:作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当 JVM 遇到一个变量赋值字节指令时会执行该操作
  • store:作用于工作内存,将赋值完毕的工作变量的值写回给主内存
  • write:作用于主内存,将store 传输的值赋值给主内存中的变量

以上 6 条只能保证单条指令的原子性,针对多条指令的组合原子保证,没有大面积加锁,所以,JVM 提供了另外两个原子指令

  • lock:作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程
  • unlock:作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用

volatile 正确使用

  • 单一赋值可以,复合运算赋值不行(i++之类)
  • 状态标志,判断业务是否结束
  • 开销较低的读,写锁策略(当读多于写,结合使用内存锁和 volatile 变量来减少同步的开销,利用 volatile 保证读取操作的可见性,利用 synchronize 保证复合操作的原子性)
  • DCL(Double Check Lock 双检锁):多线程环境下,在“问题代码处”,会执行如下操作,由于重排序导致 2,3 乱序,后果就是其他线程得到的是 null 而不是完成初始化的对象
    在这里插入图片描述
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

群集搭建【LNMP+负载均衡+高可用+跳板机】

目录 项目需求 LNMP部署 web1部署 mysql部署 php部署 nfs部署 LNMP测试 负载均衡与高可用 web2部署 lb1部署 lb2部署 验证群集 跳板机功能 测试跳板机 项目需求 实验目标:根据拓扑图搭建环境,安装论坛,创建证书通过https访问,实现…

一文搞懂 Nginx

文章目录一、前言二、NGINX 指令与上下文2.1 指令2.2 上下文三、NGINX 静态文件处理3.1 静态文件配置3.2 静态文件类型处理四、NGINX 动态路由4.1 Location 匹配4.1.1 前缀匹配4.1.2 精准匹配4.1.3 正则匹配4.1.4 优先前缀匹配4.2 Location 匹配优先级4.2.1 匹配优先级对比4.2.…

学好selenium工具,能实现你想得到的所有事情

文章目录一、介绍背景二、开发与实现2.1、部署开发环境2.2、开始码代码<demo只为提供思路>2.3、思路分析2.4、难点解析三、总结一、介绍背景 情况是这样的&#xff1a;某段时间之前&#xff0c;开发想找我用ui自动化帮他们实现一个功能&#xff1a;在系统某些时候生成报告…

[附源码]计算机毕业设计大学生心理健康测评系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

零基础CSS入门教程(17)——内边距

本章目录1.任务目标2.默认情况3.有内边距4.小结1.任务目标 上一篇介绍了外边距&#xff0c;也就是元素跟相邻元素的距离。 本篇来介绍内边距&#xff0c;顾名思义&#xff0c;内边距是指的元素内部的内容&#xff0c;与元素的边的距离 2.默认情况 <!DOCTYPE html> <…

Velero 系列文章(一):基础

概述 Velero 是一个开源工具&#xff0c;可以安全地备份和还原&#xff0c;执行灾难恢复以及迁移 Kubernetes 集群资源和持久卷。 灾难恢复 Velero 可以在基础架构丢失&#xff0c;数据损坏和/或服务中断的情况下&#xff0c;减少恢复时间。 数据迁移 Velero 通过轻松地将 …

使用VictoriaMetrics 对Prometheus的数据进行分布式存储

前言 Prometheus 就是一个很好的时序数据库&#xff0c;对于监控数据量没有超过千万级的 情况下没必要进行分布式存储。一般的监控数据存3个月以内即可&#xff0c;所以数据量并不会很大。 并且生产环境可以搞多个Proms数据源在Grafana中做统一的告警。并且在时序数据库的排名…

windows安装minikube

由于学习的需要&#xff0c;需要在windows上搭建一套可以使用的k8s学习用&#xff0c;最后选择了minikube这个安装k8s&#xff0c;本博客介绍了minikube的安装步骤&#xff0c;详细命令以及截图 1、首先是本机windows安装docker&#xff0c;具体可以参考这个 win10安装docker…

奥曲肽-葡聚糖-亲和索的偶联物TOC-Dx40-Av)|紫杉醇-DHA-右旋糖酐偶联聚合物

中文名称&#xff1a;奥曲肽-葡聚糖-亲和索的偶联物 英文名称&#xff1a;TOC-Dx40-Av 纯度&#xff1a;95% 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观:固体或粘性液体 包装&#xff1a;瓶装/袋装 溶解性&#xff1a;溶于大部分有机溶剂&#xff0…

GC 算法总结_java培训

1.标记清除压缩(Mark-Sweep-Compact) 标记清除、标记压缩的结合使用 原理java培训GC 算法总结 2.算法总结 内存效率&#xff1a;复制算法>标记清除算法>标记整理算法&#xff08;此处的效率只是简单的对比时间复杂度&#xff0c;实际情况不一定如此&#xff09;。 内…

城市云灾备,为业务连续性保驾护航

摘要&#xff1a;华为云作为中国政务云基础设施领域领导者&#xff0c;基于华为公有云技术架构的政务云平台&#xff0c;具备领先的云灾备技术实力&#xff0c;支持IaaS、PaaS等云服务云原生灾备能力。本文分享自华为云社区《城市云灾备&#xff0c;为业务连续性保驾护航》&…

Delphi Base64 的“坑”

使用 Delphi 原生的Base64编码&#xff08;System.NetEncoding 单元&#xff09; &#xff0c;编码后的字符串每隔76个字符会增加一个回车换行&#xff08;#13#10&#xff09;&#xff0c;这样就导致和其他语言对接的时候出现问题&#xff0c;特别是Base64以后再进行签名&#…

元学习:IVIF:输入不同分辨率,输出任意分辨率

Different Input Resolutions and Arbitrary Output Resolution: A Meta Learning-Based Deep Framework for Infrared and Visible Image Fusion &#xff08;不同的输入分辨率和任意输出分辨率: 基于元学习的红外和可见光图像融合深度框架&#xff09; 在本文中&#xff0c…

Web大学生网页作业成品——城市环卫管理系统后台模板(HTML+CSS+JavaScript)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

浏览器中的音视频知识总结v1.0(工作中需要和视频打交道必看)

视频是什么 视频&#xff0c;其实就是一系列连续播放的图片&#xff0c;如果1s钟播放24张图片&#xff0c;那么人眼看到的就不再是一张张独立的图片&#xff0c;而是动起来的画面。其中一张图片称为一帧&#xff0c;1s播放的图片数称为帧率。常见的帧率有24帧/s&#xff0c;30帧…

[附源码]JAVA毕业设计西安市城市绿地管理系统(系统+LW)

[附源码]JAVA毕业设计西安市城市绿地管理系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项…

Linux网络原理及编程(3)——第十三节 HTTPS

我们本文主要来介绍https&#xff0c;主要来介绍https的加密原理。 大家应该都知道http和https的区别&#xff0c;区别很简单&#xff0c;主要就是在https是采用了加密协议的&#xff0c;而http完全是在网络上裸奔的。而我们现在几乎所有的连接都用的是https 我们首先需要明白…

Flink学习26:触发器

触发器 作用&#xff1a;决定何时&#xff0c;触发窗口计算函数&#xff0c;开始计算 每个窗口都有一个默认触发器&#xff0c;也可以自定义触发器。 自定义触发器 示例1&#xff1a; 当流中元素达到5个以后&#xff0c;触发窗口计算。 import org.apache.flink.api.common.fu…

基于数据挖掘算法的服装销售平台的设计与实现(spring+spring mvc+mybatis+mysql+maven)

目 录 摘 要 I Abstract II 目 录 III 1 绪论 1 1.1 研究背景 1 1.2 研究意义 2 1.3 国内外研究现状 2 2 相关理论和开发工具 4 2.1 数据挖掘简述 4 2.2 相关数据挖掘算法概述 4 2.2.1关联规则 4 2.2.2 聚类算法 5 2.2.3 分类算法 5 2.3 文本挖掘概述 6 2.4 开发工具 7 3系统需…

5-10人的创业团队,怎么在半个月内上线一款新产品?

5~10 人的小微型创业团队&#xff0c;需不需要专业的研发协作工具&#xff1f; 随着生产力工具的价值获得更广泛的认可&#xff0c;越来越多观点认为&#xff0c;组织结构精简、业务尚未成熟的小微型团队应该尽早引入专业研发协作工具&#xff0c;完成核心竞争力的蜕变。 猴子…