什么是JMM简介

news/2024/10/21 23:20:52/

Java内存模型

  • Java内存模型(Java Memory Model)简称JMM
    • JMM与JVM的区别
    • JMM都定义了那些规范
    • 那JMM定义了什么,是围绕什么建立起来的
    • JMM是如何去解决原子性&可见性&有序性问题的?
        • 本文只是简单介绍了一下JMM的概念,下一篇,写一下volatile如何保证可见性。

Java内存模型(Java Memory Model)简称JMM

JMM与JVM的区别

很多人听到java的内存模型,感觉是不是JVM啊,其实他们是完全不同的概念,JVM是java的运行环境,而JMM是Java内存模型的缩写,它是Java程序中用来描述线程之间共享变量的规范。

下面先简单的描述JVM和JMM的区别。

JVM(Java Virtual Machine)
JVM是java的运行环境,本质上就是一个软件,负责将Java代码编译成字节码,并将字节码转化成机器码进行运行。JVM有很强的跨平台性,可以在不同的操作系统上运行Java文件,JVM是java的执行引擎,负责程序的内存管理、线程调度、垃圾回收等任务。

JMM(Java Memory Model)
JMM是Java内存模型的缩写,他是Java程序中用来描写线程之间共享变量的一种规范,JMM定义了Java程序中线程之间如何访问共享变量的规则,包括如何进行原子性操作、可见性、有序性等。JMM规定了一组规则和语义,确保在多线程情况下,对共享变量的访问是安全的和正确的。JMM的主要目标是提供一种规范,以确保Java程序在不同的硬件和操作系统上具有一致的行为。(就是为了屏蔽不同操作系统,硬件对Java线程共享变量的一种规范,来保证不同操作系统和硬件的正确使用)

因此,JVM和JMM的区别在于,JVM是Java的运行环境,负责程序的内存管理、线程调度、垃圾回收等任务。而JMM是Java程序中用来描述线程之间共享变量的规范,规定了在多线程环境下,对共享变量的访问的规则,保证共享变量访问正确性。

JMM都定义了那些规范

JMM定义了Java程序中各个变量(实例字段,静态变量和构成数组对象的元素)的访问方式

访问维度:规定所有变量都存储在主内存中,所有线程都可以访问,主内存是共享内存区域,每个线程的工作内存都是独立 , 线程 操作数据只能在工作内存中进行,然后刷回到主存。这是 Java 内存模型定义的线程基本工作方式。

读取赋值维度:规定线程线程对于变量的操作(读取赋值)必须在工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作内存,然后对变量操作,操作完成之后再写回主内存, 不可以直接操作主内存的变量,工作内存是私有内存区域,每个线程都有自己的工作内存,不可以相互访问。
在这里插入图片描述
主内存
主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发生线程安全问题。

工作内存
主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。

那JMM定义了什么,是围绕什么建立起来的

整个Java内存模型实际上是围绕着三个特征建立起来的。分别是:原子性,可见性,有序性。这三个特征是整个Java并发的基础。

原子性:
原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。
如:以下代码

X=10;  //原子性(简单的读取、将数字赋值给变量)
Y = x; //变量之间的相互赋值,不是原子操作
X++;   //对变量进行计算操作,不是院子操作,他这里其实是两步,第一将X+1,第二将新值X赋给X,多线程环境下就会出现安全问题
X = x+1;

JMM只能保证基本变量赋值的原子性,如果要保证一个代码块的原子性,提供了monitorenter 和 moniterexit 两个字节码指令,也就是 synchronized 关键字。因此在 synchronized 块之间的操作都是原子性的。

可见性
可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。对于串行程序来说,可见性是不存在的,因为我们在任何一个操作中修改了某个变量的值,后续的操作中都能读取这个变量值,并且是修改过的新值。
但在多线程环境中,线程对于共享变量的操作都是拷贝到工作内存中进行的,在操作后才会写入主内存,所以在操作过程中,另一个线程拷贝主内存变量操作,并及时写入了主内存,这个时候两个线程是隔离的,就会造成可见性问题。另外指令重排以及编译器优化也可能导致可见性问题

有序性
有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的,这样的理解并没有毛病,毕竟对于单线程而言确实如此,但对于多线程环境,则可能出现乱序现象,因为程序编译成机器码指令后可能会出现指令重排现象,重排后的指令与原指令的顺序未必一致,要明白的是,在Java程序中,倘若在本线程内,所有操作都视为有序行为,如果是多线程环境下,一个线程中观察另外一个线程,所有操作都是无序的,前半句指的是单线程内保证串行语义执行的一致性,后半句则指指令重排现象和工作内存与主内存同步延迟现象。

JMM是如何去解决原子性&可见性&有序性问题的?

原子性问题
除了JVM自身提供的对基本数据类型读写操作的原子性外,可以通过 synchronized和Lock实现原子性。因为synchronized和Lock能够保证任一时刻只有一个线程访问该代码块,自然可以保证原子性。

可见性问题
volatile关键字保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中读取新值。synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。

有序性问题
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

本文只是简单介绍了一下JMM的概念,下一篇,写一下volatile如何保证可见性。


http://www.ppmy.cn/news/499652.html

相关文章

< Linux >:入门与基本指令详解 (3)

目录 3.14、时间相关的指令 3.15、cal 指令 3.16、find 指令 3.17、grep 指令 3.18、zip/unzip 指令 3.19、tar 指令 3.20、bc 指令 3.21、uname -r 指令 3.22、重要的几个热键 [Tab] [ctrl]-c [ctrl]-d 3.23、关机 3.24、tac 指令 3.14、时间相关的指令 date 指定…

GHM

论文题目:Gradient Harmonized Single-stage Detector 论文链接:https://arxiv.org/pdf/1811.05181.pdf 解决 正、负样本简单和困难眼样本之间的不均衡问题,负样本和简单样本数量多。 之前工作 基于实例挖掘: 为了解决之前的不平衡问题&…

< Linux >:Linux 进程概念 (2)

目录 3.5、查看进程 3.6、通过系统调用接口获取时实进程的标识符 3.7、通过系统调用接口创建子进程 - fork 初识 3.5、查看进程 [HJMhjmlcc ~]$ clear [HJMhjmlcc ~]$ pwd /home/HJM [HJMhjmlcc ~]$ ls [HJMhjmlcc ~]$ touch mytest.c [HJMhjmlcc ~]$ ls mytest.c [HJMhjml…

< Linux >:shell命令初步认识,Linux权限

目录 1、Shell命令以及运行原理 2、Linux权限的概念 3、Linux权限管理 3.1、文件访问者的分类(人) 3.2、文件类型和文件权限属性(事物属性) 3.3、粘滞位 4、file指令 1、Shell命令以及运行原理 Linux严格意义上说的是一个操作系统,我们称之为" 核心(ker…

< Linux >:Linux 环境基础开发工具使用 (3)

目录 一、Linux 调试器 - gdb 的使用 1.1、背景 1.2、开始使用 1.3、理解 二、Linux项目自动化构建工具 - make与Makefile(makefile) 2.1、背景 2.2、项目结构 三、Linux 系统中第一个小程序-倒计时 四、Linux 系统中第二个小程序-进度条 一、L…

低版本Docker升级高版本Docker【详细教程、成功避坑】

🎈 作者:互联网-小啊宇 🎈 简介: CSDN 运维领域创作者、阿里云专家博主。目前从事 Kubernetes运维相关工作,擅长Linux系统运维、开源监控软件维护、Kubernetes容器技术、CI/CD持续集成、自动化运维、开源软件部署维护…

Gorm many2many关系中如何使用预加载查询

Gorm many2many关系中如何使用预加载查询 gorm中,若两个实体A、B之间关系为m:n。如果查询A时候需要Preload(B),那么db查询之前需要设置 db.SetupJoinTable()。 例子: Person : Address m : n。 type Person struct {ID int …

深度学习实例分割篇——Mask RCNN原理详解篇

🍊作者简介:秃头小苏,致力于用最通俗的语言描述问题 🍊专栏推荐:深度学习网络原理与实战 🍊近期目标:写好专栏的每一篇文章 🍊支持小苏:点赞👍🏼、…