理解什么是 JMM

2023-11-07

理解什么是 JMM

本文已收录至 GitHub https://github.com/yifanzheng/java-notes

Java 虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——Java 内存模型。也就是说,Java 内存模型是 Java 虚拟机中定义的一种并发编程的底层模型机制。

JMM 概念

JMM(Java Memory Model)就是 Java 内存模型,是 Java 虚拟机规范中所定义的一种内存模型。因为在不同的硬件生产商和不同的操作系统下,内存的访问有一定的差异,所以会造成相同的程序运行在不同的系统上会出现各种问题。因此 Java 内存模型屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证一致的并发效果。

JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

JMM 的规定:

  • 所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

  • 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。

  • 线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量。

  • 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存中转来完成。

总之,Java 内存模型规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。

JMM 的抽象示意图:

JMM

从图中可以更直观的看出,每个线程的内存都是独立的,线程对变量的操作只能在工作内存中进行,然后刷回到主存。这便是 Java 内存模型定义的线程基本工作方式。

JMM 的八种内存交互操作

为了更直观,先来看看这张图吧:

JMM 内存交互

  1. lock(锁定):作用于主内存中的变量,一个变量在同一时间只能被一个线程锁定,即把变量标识为线程独占状态。
  2. read(读取):作用于主内存变量,表示把一个变量值从主内存传输到线程的工作内存中,以便下一步的 load 操作使用。
  3. load(载入):作用于线程的工作内存的变量,表示把 read 操作从主内存中读取的变量值放到工作内存的变量副本中(副本是相对于主内存的变量而言的)。
  4. use(使用):作用于线程的工作内存中的变量,表示把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作。
  5. assign(赋值):作用于线程的工作内存的变量,表示把执行引擎返回的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作。
  6. store(存储):作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便下一步的 write 操作使用。
  7. write(写入):作用于主内存的变量,表示把 store 操作从工作内存中得到的变量的值放入主内存的变量中。
  8. unlock(解锁):作用于主内存的变量,表示把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

JMM 还规定了以上八种操作需按照如下规则进行:

  • 不允许read 和 load、store 和 write 操作之一单独出现,也就是 read 操作后必须 load,store 操作后必须 write。即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
  • 不允许线程丢弃它最近的 assign 操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • 不允许线程将没有 assign 的数据从工作内存同步到主内存。
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。也就是对变量实施 use 和 store 操作之前,必须经过 load 和 assign 操作。
  • 一个变量同一时间只能有一个线程对其进行 lock 操作。但 lock 操作可以被同一条线程重复执行多次,多次 lock 之后,必须执行相同次数 unlock 才可以解锁。
  • 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新 load 或 assign 操作初始化变量的值。
  • 如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其他线程锁住的变量。
  • 一个线程对一个变量进行 unlock 操作之前,必须先把此变量同步回主内存。

JMM 三大特征

JMM 三大特征分别是:原子性,可见性,有序性。整个 JMM 实际上也是围绕着这三个特征建立起来的,并且也是 Java 并发编程的基础。

原子性

原子性是指一个操作是不可分割、不可中断的,要么全部执行成功要么全部执行失败。

JMM 只能保证对基本数据类型的变量的读写操作是原子性的,但 long 和 double 除外(long 和 double 的非原子性协定)。

我们来看看下面的例子:

int x = 1;
int y = x;
x ++;

上面三行代码只有第一行是原子性操作,基本类型赋值操作,必定是原子性操作。

第二行代码先读取 x 变量的值,再进行赋值给 y 变量,进行了两个操作,不能保证原子性。

第三行代码先读取 x 变量的值,再进行加 1,最后再赋值给 x 变量,进行了三个操作,不能保证原子性。

在并发环境下,为了保证原子性,Java 提供了 synchronized 关键字。因此在 synchronized 修饰的代码块之间的操作都是原子性的。

可见性

可见性是指所有线程都能看到共享内存的最新状态。即当一个线程修改了一个共享变量的值时,其他线程能够立即看到该变量的最新值。

对于可见性问题,Java 是提供了一个 volatile 关键字来保证可见性。当一个共享变量被 volatile 关键字修饰时,这个变量被修改后会立即刷新到主内存,保证其他线程看到的值一定是最新的。

除了 volatile 关键字之外,final 和 synchronized 也能实现可见性。

final 关键字修饰的变量,在构造器中一旦初始化完成,如果没有对象逸出(指对象没有初始化完成就可以被别的线程使用),那么其他线程都就可以看见 final 修饰的变量。

synchronized 的原理是,线程进入 synchronized 代码块后,线程会获取到 lock,将会清空本地内存,然后从主内存中拷贝共享变量的最新值到本地内存作为副本,执行代码,又将修改后的副本值刷新到主内存中,最后线程执行 unlock。

有序性

有序性是指程序执行的顺序按照代码的先后顺序执行。

在 Java 中,可以通过 volatile 和 synchronized 关键字来保证多线程之间操作的有序性。

volatile 关键字是通过在主存中加入内存屏障来达到禁止指令重排序,来保证有序性。

synchronized 关键字原理是,一个变量在同一时刻只能被一个线程 lock,并且必须 unlock 后,其他线程才可以重新 lock,使得被 synchronized 修饰的代码块在多线程之间是串行执行的。

参考

[1] Java 内存模型(JMM)总结: https://zhuanlan.zhihu.com/p/29881777

[2] Java 内存模型(JMM)详解: https://juejin.im/post/6844903986663800845

[3] 再有人问你 Java 内存模型是什么,就把这篇文章发给他。: https://www.hollischuang.com/archives/2550

[4] Java Memory Model: http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

因为是个人学习笔记,难免存在一些错误或纰漏,也请小伙伴们指正。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

理解什么是 JMM 的相关文章

  • 按键时关闭 ModalWindow

    我希望能够在用户按下某个键 在我的例子中是 ESC 时关闭 ModalWindow 我有一个用于按键的 Javascript 侦听器 它调用取消按钮 ID 的单击事件 jQuery modalWindowInfo closeButtonId
  • 日期语句之间的 JPQL SELECT [关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我想将此 SQL 语句转换为等效的 JPQL SELECT FROM events WHERE events date BETWE
  • 不同帐户上的 Spring Boot、JmsListener 和 SQS 队列

    我正在尝试开发一个 Spring Boot 1 5 应用程序 该应用程序需要侦听来自两个不同 AWS 帐户的 SQS 队列 是否可以使用 JmsListener 注解创建监听器 我已检查权限是否正确 我可以使用 getQueueUrl 获取
  • Spring应用中Eureka健康检查的问题

    我正在开发一个基于 Spring 的应用程序 其中包含多个微服务 我的一个微服务充当尤里卡服务器 到目前为止一切正常 在我所有其他微服务中 用 EnableEurekaClient 我想启用这样的健康检查 应用程序 yml eureka c
  • 如何使用assertEquals 和 Epsilon 在 JUnit 中断言两个双精度数?

    不推荐使用双打的assertEquals 我发现应该使用带有Epsilon的形式 这是因为双打不可能100 严格 但无论如何我需要比较两个双打 预期结果和实际结果 但我不知道该怎么做 目前我的测试如下 Test public void te
  • Pig Udf 显示结果

    我是 Pig 的新手 我用 Java 编写了一个 udf 并且包含了一个 System out println 其中的声明 我必须知道在 Pig 中运行时该语句在哪里打印 假设你的UDF 扩展了 EvalFunc 您可以使用从返回的 Log
  • 如何在 Spring 中禁用使用 @Component 注释创建 bean?

    我的项目中有一些用于重构逻辑的通用接口 它看起来大约是这样的 public interface RefactorAwareEntryPoint default boolean doRefactor if EventLogService wa
  • jQuery AJAX 调用 Java 方法

    使用 jQuery AJAX 我们可以调用特定的 JAVA 方法 例如从 Action 类 该 Java 方法返回的数据将用于填充一些 HTML 代码 请告诉我是否可以使用 jQuery 轻松完成此操作 就像在 DWR 中一样 此外 对于
  • 来自 dll 的 Java 调用函数

    我有这个 python 脚本导入zkemkeeperdll 并连接到考勤设备 ZKTeco 这是我正在使用的脚本 from win32com client import Dispatch zk Dispatch zkemkeeper ZKE
  • 没有 Spring 的自定义 Prometheus 指标

    我需要为 Web 应用程序提供自定义指标 问题是我不能使用 Spring 但我必须使用 jax rs 端点 要求非常简单 想象一下 您有一个包含键值对的映射 其中键是指标名称 值是一个简单的整数 它是一个计数器 代码会是这样的 public
  • java.lang.IllegalStateException:提交响应后无法调用 sendRedirect()

    这两天我一直在尝试找出问题所在 我在这里读到我应该在代码中添加一个返回 我做到了 但我仍然得到 java lang IllegalStateException Cannot call sendRedirect after the respo
  • 在 junit 测试中获取 javax.lang.model.element.Element 类

    我想测试我的实用程序类 ElementUtils 但我不知道如何将类作为元素获取 在 AnnotationProcessors 中 我使用以下代码获取元素 Set
  • 内部类的构造函数引用在运行时失败并出现VerifyError

    我正在使用 lambda 为内部类构造函数创建供应商ctx gt new SpectatorSwitcher ctx IntelliJ建议我将其更改为SpectatorSwitcher new反而 SpectatorSwitcher 是我正
  • volatile、final 和synchronized 安全发布的区别

    给定一个带有变量 x 的 A 类 变量 x 在类构造函数中设置 A x 77 我们想将 x 发布到其他线程 考虑以下 3 种变量 x 线程安全 发布的情况 1 x is final 2 x is volatile 3 x 设定为同步块 sy
  • java.io.Serialized 在 C/C++ 中的等价物是什么?

    C C 的等价物是什么java io Serialized https docs oracle com javase 7 docs api java io Serializable html 有对序列化库的引用 用 C 序列化数据结构 ht
  • Cucumber 0.4.3 (cuke4duke) 与 java + maven gem 问题

    我最近开始为 Cucumber 安装一个示例项目 并尝试使用 maven java 运行它 我遵循了这个指南 http www goodercode com wp using cucumber tests with maven and ja
  • 最新的 Hibernate 和 Derby:无法建立 JDBC 连接

    我正在尝试创建一个使用 Hibernate 连接到 Derby 数据库的准系统项目 我正在使用 Hibernate 和 Derby 的最新版本 但我得到的是通用的Unable to make JDBC Connection error 这是
  • 干净构建 Java 命令行

    我正在使用命令行编译使用 eclipse 编写的项目 如下所示 javac file java 然后运行 java file args here 我将如何运行干净的构建或编译 每当我重新编译时 除非删除所有内容 否则更改不会受到影响 cla
  • 在java中为组合框分配键

    我想添加一个JComboBox在 Swing 中这很简单 但我想为组合中的每个项目分配值 我有以下代码 JComboBox jc1 new JComboBox jc1 addItem a jc1 addItem b jc1 addItem
  • 使用 svn 1.8.x、subclise 1.10 的 m2e-subclipse 连接器在哪里?

    我读到 m2e 的生产商已经停止生产 svn 1 7 以外的任何版本的 m2e 连接器 Tigris 显然已经填补了维护 m2e subclipse 连接器的空缺 Q1 我的问题是 使用 svn 1 8 x 的 eclipse 更新 url

随机推荐

  • Incorrect integer value: '' for column 'id' at row 1 错误解决办法

    最近一个项目 在本地php环境里一切正常 ftp上传到虚拟空间后 当执行更新操作 我的目的是为了设置id为空 set id 时提示 Incorrect integer value for column id at row 1 解决办法 方法
  • 广工人福利,openwrt+gduth3c通过inode认证,妈妈再也不用担心我要用电脑开wifi了

    刚开校园网的时候 天天都只能用电脑开wifi 用类似于360wifi 猎豹wifi之类的软件要经常开着电脑 而且电脑网卡发射功率又小 上个厕所wifi就断了 睡觉前在床上还没wifi用 超级不爽 于是从家里面拿来了放在自己房间挂迅雷百度云的
  • x86下的C函数调用惯例

    1 从汇编到C 1 1 汇编语言的局限性 汇编语言是一种符号化了的机器语言 machine code 即用指令助记符 符号地址 标号等符号书写程序的语言 汇编语句与机器语句一一对应 它只是把每条指令及数据用便于记忆的符号书写而已 汇编语言
  • 用自己的数据增量训练预训练语言模型

    预训练模型给各类NLP任务的性能带来了巨大的提升 预训练模型通常是在通用领域的大规模文本上进行训练的 而很多场景下 使用预训练语言模型的下游任务是某些特定场景 如金融 法律等 这是如果可以用这些垂直领域的语料继续训练原始的预训练模型 对于下
  • spring配置文件解读——applicationContext.xml

    spring的配置文件 applicationContext xml 听着晴天看星晴的博客 CSDN博客
  • binutils internal struct

    http fossies org dox binutils 2 23 2 structelf internal sym html dl iterate phdr REPAIR RAX inline hook
  • [动态规划] leetcode 416. 分割等和子集

    问题描述 分割等和子集 给你一个只包含正整数的非空数组 nums 请你判断是否可以将这个数组分割成两个子集 使得两个子集的元素和相等 例子 输入nums 1 5 11 5 输出true 动态规划求解 这是一个0 1背包问题的变种 也就是每种
  • Idea工具使用经典总结

    安装教程 下载地址 https www jetbrains com idea download section windows 准备idea ideaIU 2017 2 3 exe 软件与激活包 JetbrainsCrack 2 6 9 r
  • 设备节点如何与设备驱动关联

    1 上层应用如何调用设备驱动 1 在linux中一切皆是文件 设备驱动程序对上层应用程序来说和普通文件没什么差异 2 上层应用程序通过设备节点来访问驱动程序 在驱动程序注册到内核后 用申请到的主次设备号来创建设备节点 2 向内核注册字符驱动
  • 【已解决】Error: Unable to access jarfile .\xxxx.jar

    报错类型 Error Unable to access jarfile xxxx jar 复现工具的时候 通过命令 java jar xxxx jar 运行 jar 包报了这个错误 报错原因是 在命令行中出现的路径下找不到 xxxx jar
  • micro-app在vue-element-admin中一些使用研究

    1 简述 本文承接上一篇micro app在vue element admi中的搭建 对micro app在vue element admin中的一些平时开发中常用的功能做了一些研究 本文代码 2 路由 关于路由 这边从两方面进行研究 一方
  • ass字幕格式

    ssa ass字幕格式全解析 内容 一 概述 二 文件各个部分解析 三 各种类型的行 四 Script Info 部分的标题行 五 v4 Styles 部分的风格行Style 六 Events 事件部分的对话行Dialogue 七 Even
  • 高通功耗调试18之Tsensor中断频繁触发导致低温下待机功耗高的问题

    问题背景 在内核4 9及之后的版本 低于5C环境温度下待机 由于触发了Tsensor的低温保护机制 可能会遇到较频繁 的tsens中断 11 22 07 12 27 914969 0 0 W GICv3 gic show resume ir
  • [echarts]柱状图的点击事件

    先来一段简洁的写echarts图表的代码 这样获取echarts的dom节点是因为 如果将柱状图封装成了一个组件 在一个页面中多次使用 若还是按常规获取dom节点 会报一个警告 let charts echarts getInstanceB
  • Linux驱动开发(应用程序如何调用驱动)

    1 添加读写接口 1 在应用代码中 2 在驱动代码中 2 应用和驱动之间的数据交换 1 copy from user 用来将数据从用户空间复制到内核空间 2 copy to user 用来将数据从内核空间复制到用户空间 3 write和re
  • DAPM之一:概述

    DAPM Dynamic Audio Power Management 对应结构体是snd soc dapm widget和snd soc dapm route 对应的操作函数是snd soc dapm new controls snd s
  • C++对象模型和this指针

    C 对象模型和this指针 成员变量和成员函数分开存储 在C 中 类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 include
  • 逐梦C++补遗篇之一:cout与cerr的区分

    逐梦C 补遗篇之一 cout与cerr的区分 1 从定义看区别 cout 标准输出流 带缓冲 默认输出目的地为屏幕 可以被重定向 cerr 标准错误输出 不带缓冲 输出目的地为屏幕 一般不被重定向 缓冲 带缓冲 就是系统会为你分配一个缓冲区
  • 精彩观点一览

    7月20日下午 大模型的发展路径论坛于北京成功举办 大模型的发展路径论坛作为2023中国互联网大会的分论坛之一 由中国互联网协会人工智能工作委员会承办 中国信通院云计算与大数据研究所 华为云大数据与AI业务协办 并得到阿里云 北京智源研究院
  • 理解什么是 JMM

    理解什么是 JMM 本文已收录至 GitHub https github com yifanzheng java notes Java 虚拟机是一个完整的计算机的一个模型 因此这个模型自然也包含一个内存模型 Java 内存模型 也就是说 J