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. SQL基础知识扫盲

    @ 目录 SQL & 数据库基础知识扫盲 SQL是什么? 数据库是什么? 挺身入局,实践出真知 DBMS初体验 MySQL:初体验 Oracle:初体验 PostgreSQL:初体验 Demo ...

  2. odoo开发教程五:高级视图

    树视图 tree视图表现出来是列表视图,列表中一行一纪录.可以根据每行纪录的某字段值不同而把每行以不同样式显示. decoration-{样式}="条件" 样式主要有: bf(fo ...

  3. 使用containerd从0搭建k8s(kubernetes)集群

    准备环境 准备两台服务器节点,如果需要安装虚拟机,可以参考<wmware和centos安装过程> 机器名 IP 角色 CPU 内存 centos01 192.168.109.130 mas ...

  4. Mybatis的ResultMap对column和property的理解

    Mybatis的ResultMap对column和property的理解 首先,先看看这张图,看能不能一下看明白: select元素有很多属性(这里说用的比较多的): id:命名空间唯一标识,可以被用 ...

  5. C++ 学习笔记 (一)

    C++标准化组织 https://isocpp.org/std/status http://open-std.org/JTC1/SC22/WG21/ why C++王者归来? https://cool ...

  6. React框架学习基础篇-HelloReact-01

    一直想掌握一门前端技术,于是想跟着张天宇老师学习,便开始学习React,以此来记录一下我的学习之旅. 学习一门新的技术首先是去官网看看,React官网链接是[https://zh-hans.react ...

  7. 【题解】ABC293E Sol

    题目大意 给定整数 \(A,X,M\),求 \(\sum\limits^{X-1}_{i=0} A^i\) 对 \(M\) 取模的值. 数据范围:\(1 \le A,M \le 10^9\),\(1 ...

  8. Harbor 容器镜像仓库

    Harbor仓库概述 Docker官⽅提供了Registry镜像仓库,但是Registry的功能相对简陋.Harbor是VMware公司提供的⼀款镜像仓库,提供了权限控制.分布式发布.强⼤的安全扫描与 ...

  9. 在 Arch 配置 i3-wm 终端模拟器 xterm

    在 Arch 配置 i3-wm 终端模拟器 xterm 关于怎么在 Arch 安装 i3-wm 可以查看上一篇文章 https://www.cnblogs.com/shadow-/p/17572589 ...

  10. C# Task 实现任务超时取消、超时取消然后重试 超过重试最大次数就结束。

    任务超时取消 示例 public static async Task TimeoutCancelTask() { CancellationTokenSource cts = new Cancellat ...