1、概述

我们常说的JMM指的是Java内存模型(Java Memory Model,JMM),主要用于控制Java程序解决线程间如何通信和数据同步,JMM规范了多线程访问共享内存时的 可见性、有序性和原子性

  • 所有的共享变量都存在主内存中;
  • 每个线程都保存了一份该线程使用到的共享变量的副本
  • 如果线程A与线程B之间要通信的话,必须经历下面2个步骤:

    线程A将本地内存A中更新过的共享变量刷新到主内存中去。

    线程B到主内存中读取线程A之前已经更新过的共享变量。

因此,线程A无法直接访问线程B的工作内存,那是因为工作内存是线程独有的,线程间通信必须借助主内存,这也是JMM中的规定。当主内存中的共享变量被某个线程更新时,JMM会通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证。因此通过JMM规范,有效的解决了以下问题:

  • 可见性问题:JMM保证对于一个线程对变量的修改,其他线程能够立即看到这个修改,从而避免了线程之间读取数据不一致的问题;

  • 有序性问题:JMM保证程序的执行顺序是有序的,即按照代码的编写顺序执行,从而避免了出现代码执行顺序混乱的问题;

  • 原子性问题:JMM保证对单个变量的读取和写入操作是原子性的,即不会出现数据竞争问题。

2、JMM内存模型的实现

2.1、简介

Java内存模型规范了JVM如何按需禁用缓存和编译优化,具体包括volatilesynchronizedfinal这几个关键字,以及Happens-Before规则。

2.2、原子性

原子性指的是指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰。在Java中,最常用的便是使用关键字synchronized进行原子性的保证。

2.3、可见性

一个未声明volatile的变量,都是从各自的cpu缓存获取数据,线程更新数据之后,其他线程无法获取最新的值。而使用volatile声明的变量,表明禁用缓存,更新数据直接更新到内存中,每次获取数据都是直接内存获取最新的数据。线程之间的数据都是相互可见的。

可见性来自happens-before规则,happens-before用来描述两个操作的内存可见性,如操作Ahappens-before操作B,那么A的结果对于B是可见的,前面的一个操作结果对后续操作是可见的happens-before定义了以下几个规则:

  • 解锁操作happens-before同一把锁的加锁操作。
  • volatile 字段的写操作happens-before同一字段的读操作。
  • 线程的启动操作happens-before该线程的第一个操作。
  • Ahappens-beforeB,且Bhappens-beforeC,那么Ahappens-beforeC。happens-before具有传递性。

2.4、有序性

指程序是有序的按照一定的顺序运行,这一特性主要是针对于操作系统中对程序指令进行重排序造成的并发乱序问题。为了性能和便捷,在JMM中指明,再不改变程序执行结果的前提下,允许编译器和处理器对程序优化进行重排序。

在Java中,可以使用synchronizedvolatile来保证多线程之间操作的有序性。实现方式有所区别:

  • volatile关键字会禁止指令重排;
  • synchronized关键字保证同一时刻只允许一条线程操作。

如果代码没有依赖关系,JVM编译优化可以对他们随意的重排序,比如method1方法没有依赖关系,进行重排序:

int a=0, b=0;
public void method1() {
int r2 = a;
b = 1;
} public void method2() {
int r1 = b;
a = 2;
}

此时在多线程环境下,两个线程交替运行method1method2方法:重排序后r1r2分别是0,0

那如何解决重排序的问题呢?答案就是将变量声明为volatile,比如a或者b变量声明volatile。比如b声明为volatile,此时b的赋值操作要happens-before r1的赋值操作。

int a=0;
volatile int b=0;
public void method1() {
int r2 = a;
b = 1;
} public void method2() {
int r1 = b;
a = 2;
}

同一个线程顺序也满足happens-before关系以及传递性,可以得到r2的赋值happens-before a的赋值。也就表明对a赋值时,r2已经完成赋值了。也就不可能出现r1r200的结果。

总结

Java内存模型(Java Memory Model,JMM)定义了Java程序中多线程之间共享变量的访问规则,以及线程之间的交互行为。它规定了线程如何与主内存和工作内存交互,以确保多线程程序的可见性、有序性和一致性。

  • 可见性:使用volatile声明变量,数据读取直接从内存中读取,更新也是强制刷新缓存,并同步到主内存中。
  • 有序性:使用volatile声明变量,确保编译优化不会重排序该字段。
  • Happens-Before: 前面一个操作的结果对后续操作是可见的

参考

了解JAVA内存模型(JMM)的更多相关文章

  1. Java内存模型JMM与可见性

    Java内存模型JMM与可见性 标签(空格分隔): java 1 何为JMM JMM:通俗地讲,就是描述Java中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这 ...

  2. 多线程并发之java内存模型JMM

    多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果 ...

  3. Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)

    JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...

  4. 全面理解Java内存模型(JMM)及volatile关键字(转载)

    关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...

  5. 全面理解Java内存模型(JMM)及volatile关键字(转)

    原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...

  6. 什么是Java内存模型(JMM)

    什么是java内存模型 缓存一致性问题 在现代计算机中,因为CPU的运算速度远大于内存的读写速度,因此为了不让CPU在计算的时候因为实时读取内存数据而影响运算速度,CPU会加入一层缓存,在运算之前缓存 ...

  7. 对多线程java内存模型JMM

    多线程概念的引入体现了人类重新有效压力寨计算机.这是非常有必要的,由于所涉及的读数据的过程中的一般操作,如从磁盘.其他系统.数据库等,CPU计算速度和数据读取速度已经严重失衡.假设印刷过程中一个线程将 ...

  8. 深入理解Java内存模型JMM与volatile关键字

    深入理解Java内存模型JMM与volatile关键字 多核并发缓存架构 Java内存模型 Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型来建立的,Java线程内存模型是标准化的,屏蔽 ...

  9. Java内存模型(JMM)详解

    在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...

  10. Java并发编程:Java内存模型JMM

    简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...

随机推荐

  1. .Net后台调用js,提示、打开新窗体、关闭当前窗体

    .Net后台调用js,提示.关闭当前窗体.打开新窗体 Response.Write("<script>window.alert('支付成功!');window.open('/Jk ...

  2. Yolov5代码解析(输入端、BackBone、Neck、输出端))

    [深度学习]总目录 输入端:数据增强.锚框计算等. backbone:进行特征提取.常用的骨干网络有VGG,ResNet,DenseNet,MobileNet,EfficientNet,CSPDark ...

  3. PHP正则按照从大到小的SIGN签名算法

    <?php/** * 签名算法 * @param unknown $key_id S_KEY(商户KEY) * @param unknown $array 例子:$array = array(' ...

  4. "Process finished with exit code 1" 进程结束

    问题描述 :  springboot     程序运行出现以下情况 没有错误日志  返回运行结束 状态码  1     状态码为 1 的时候表示程序不是异常终止 连接到目标VM, 地址: ''127. ...

  5. n皇后问题的分析和实现

    N皇后问题的分析和实现 1.实现要求 2.代码实现 1.实现要求 在n*n的方格棋中,放置n个皇后,要求每个皇后不同行,不同列,不同对角线 以行为依据,遍历行,判断行对应的列是否符合要求 判定要求: ...

  6. 万字长文 | Hadoop 上云: 存算分离架构设计与迁移实践

    一面数据原有的技术架构是在线下机房中使用 CDH 构建的大数据集群.自公司成立以来,每年都保持着高速增长,业务的增长带来了数据量的剧增. 在过去几年中,我们按照每 1 到 2 年的规划扩容硬件,但往往 ...

  7. C补充

    C语言之unsigned 与signed 无符号与有符号数据的操作区别在于当最高位,当最高位为0时都一样: 在C中,默认的基础数据类型均为signed,现在我们以char为例,说明(signed) c ...

  8. [HUBUCTF 2022 新生赛]simple_RE

    [HUBUCTF 2022 新生赛]simple_RE 查壳,64位 找main函数,F5查看伪代码,简单分析一下 int __cdecl main(int argc, const char **ar ...

  9. Composer 镜像原理 (1) —— 初识 Composer

    相关文章 Composer 镜像原理 (1) -- 初识 Composer Composer 镜像原理 (2) -- composer.json Composer 镜像原理 (3) -- 完结篇 何为 ...

  10. .NET周刊【7月第4期 2023-07-23】

    国内文章 你知道.NET的字符串在内存中是如何存储的吗? https://www.cnblogs.com/artech/p/string-memory-layout.html 毫无疑问,字符串是我们使 ...