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. mysql安装教程【安装版】和Navicat-for-MySQL破解版

    傻瓜式mysql安装教程[安装版]https://blog.csdn.net/qq_59636442/article/details/123058454 Navicat-for-MySQL下载链接:h ...

  2. 混合编程python与C++

    上个版本: 只是用到ctypes进行传输, 这次将python服务端更改为C++服务端,方便后续维护. 本文实现功能: python传输图片给C++, C++接受图片后对图片进行处理,并将结果返回给p ...

  3. 关于Unity3D第一视角下镜头穿墙的问题解决方法

    昨天做室内模型的时候,遇到一个非常棘手的问题,那就是第一视角在室内运行的时候,会出现穿墙的效果.类似下图效果,在靠近墙壁的时候,出现了镜头看见了墙壁外的情况,很显然这是不符合逻辑的.我们要做的就是避免 ...

  4. Rust 内存系统

    第四章 内存系统 不同的编程语言对内存有着不同的管理方式. 按照内存的管理方式可将编程语言大致分为两类: 手动管理类 手动内存管理类需要开发者使用malloc和free等函数显式管理内存. 自动内存管 ...

  5. zabbix监控服务器php

    1 修改php配置文件 /apollo/env/php/thirdparty.php-7.3/etc/php-fpm.conf,添加pm.status pm.status_path = /phpfpm ...

  6. 认识Dubbo与RPC

    关注王有志,分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群:共同富裕的Java人 开个新坑,和大家一起学习Dubbo 3.X.我们按照一个由浅入深顺序来学习,先从使用Dubbo开始,再深 ...

  7. PHP代码获取网址参数的数据,请收藏。

    <? echo $_SERVER['HTTP_HOST']."<br>"; #localhost echo $_SERVER['PHP_SELF']." ...

  8. springboot自定义消息转换器

    import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; im ...

  9. Prometheus-3:一文详解promQL

    读前提示: 本文字数较多且紧凑,最好预留15min一次性看完,好营养,易吸收. promQL详解 Prometheus提供了内置的数据查询语言PromQL(全称为Prometheus Query La ...

  10. Send files or execute commands over SSH

    1. 配置 SSH Server ----公钥和私钥的配置---- 假设有两台服务器,A是Jenkins构建服务器,B是应用服务器,A构建好应用之后,将包传到B进行发布. 在A上面执行 ssh-key ...