JMM 内存模型、volatile 关键字保证有序性和可见性相关问题总结

简介: 临近秋招,备战暑期实习,祝大家每天进步亿点点!Day19

本篇总结的是 JMM内存模型,volatile 关键字保证有序性和可见性的原理,happens-before原则。

image.png

参考文章:Java面试官告诉你JMM是什么和面什么、阿里实习面经、「阿里面试系列」分析Synchronized原理,让面试官仰望、happens-before理解和应用、面试官:说说什么是 Java 内存模型(JMM)?、面试官:volatile是如何保证可见性和有序性的?、happen-before原则、通俗易懂讲解happens-before原则


1、什么是Java内存模型?

首先,要知道,Java 内存模型指的是 JMM,而不是运行时数据区哦~


Java 语言为了保证并发编程中可以满足原子性、可见性及有序性,于是推出了一个概念就是 JMM 内存模型。


JMM 内存模型,目的是为了在多线程条件下,使用共享内存进行数据通信时,通过对多线程程序读操作、写操作行为规范约束,来尽量避免多次内存数据读取不一致、编译器对代码指令重排序、处理器对代码乱序执行等带来的问题。


JMM 内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。

JMM 内存模型将内存主要划分为主内存和工作内存两种。规定 所有的变量都存储在主内存中,每条线程都拥有自己的工作内存,线程的工作内存中保存了该线程所需要用到的变量在主内存中的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读、写主内存。

不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要线程自己的工作内存和主存之间进行数据交互。

如图所示:


image.png

image.png

JMM 内存模型工作内存、主内存和 JVM 内存有什么关系?


JMM 内存模型中,工作内存和主内存其实跟JVM内存的划分是在不同层次上进行的,是自己的一套抽象概念,大概可以理解为,主内存对应的是 Java 堆中的对象实例部分,而工作内存对应的则是栈中的部分区域。


2、JMM 定义了哪些操作来完成主内存和工作内存的交互操作?

JMM 定义了8 个操作来完成主内存和工作内存的交互操作:


① 首先是从 lock 加锁开始,作用于主内存的变量,把一个变量标识为一条线程独占的状态;

② read 读取,作用于主内存变量,将一个变量的值从主内存读取到工作内存中;

③ load 加载,作用于工作内存的变量,把 read 读取到的值加载到工作内存的变量副本中;

④ use 使用,作用于工作内存的变量,把工作内存中变量的值传递给执行引擎使用,每当虚拟机遇到一个需要使用变量值的字节码指令时将会执行这个操作;

⑤ assign 赋值,作用于工作内存的变量,把从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个需要使用变量值的字节码指令时将会执行这个操作;

⑥ store 存储,作用于工作内存的变量,把工作内存中变量的值传送回主内存中,以便随后的 write 的操作;

⑦ write 写入,作用于主内存的变量,把 store 得到的值放入主内存的变量中;

⑧ 最后是 unlock 解锁,把主内存中处于锁定状态的变量释放出来,流程到这一步就结束了。

如图所示:

image.png

JMM 基本可以说是围绕着在并发中如何处理这三个特性而建立起来的,也就是原子性、可见性、以及有序性。


3、volatile关键字是如何保证可见性的?

当一个共享变量被 volatile 修饰时,它会保证修改的值会被立即更新到主内存中,当有其他线程读取该值时,也不会直接读取工作内存中的值,而是直接去主内存中读取。


而普通的共享变量不能保证可见性的,因为普通共享变量被修改后,写入了工作内存中,什么时候写入主内存其实是不可知的,当其他线程去读取是,此时无论是工作内存还是主内存,可能还是原来的值,因此无法保证可见性。


被volatile关键字修饰的变量,在每个写操作之后,都会加入一条store内存屏障命令,此命令强制将此变量的最新值从工作内存同步至主内存;在每个读操作之前,都会加入一条load内存屏障命令,此命强制从主内存中将此变量的最新值加载至当前线程的工作内存中。


4、volatile关键字是如何保证有序性的?

volatile 可以禁止指令重排,保证程序会严格按照代码的先后顺序执行。


加了volatile 修饰的共享变量,通过内存屏障解决多线程下的有序性问题。原理如下:


在每个 volatile 写操作的前面插入一个 StoreStore 屏障

在每个 volatile 写操作的后面插入一个StoreLoad屏障

在每个 volatile 读操作的后面插入一个LoadLoad屏障

在每个 volatile 读操作的后面插入一个LoadStore屏障

volatile 在写操作前后插入了内存屏障后生成的指令序列示意图如下:

image.png

volatile 在读操作后面插入了内存屏障后生成的指令序列示意图如下:

image.png

5、happens-before原则

happens-before 的概念:


JMM 可以通过 happens-before 关系向程序员提供跨线程的内存可见性保证(如果 A 线程的写操作 a 与 线程 B的读操作 b 之间存在 happens-before 关系,尽管 a 操作和 b 操作在不同的线程中执行,但 JMM 向程序员保证 a 操作将对 b 操作可见)。


happens-before 具体定义:前面一个操作的结果对后续操作是可见的。


1)如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。


两个操作之间存在 happens-before 关系,并不意味着一定要按照 happens-before 原则制定的顺序来执行。只要两个操作在指令重排序之后的执行结果与按照 happens-before 关系来执行的结果一致,那么这种重排序也是合法的。

happens-before 的 8 大原则:


① 单线程happen-before原则:同一个线程中前面的所有写操作对后面的操作可见。

② 锁的happen-before原则:同一个锁的 unlock 操作 happen-before 此锁的 lock 操作。这条规则是指对一个对象的解锁 happen-before 于后续对这个对象的加锁。

③ volatile的happen-before原则:对一个 volatile 变量的写操作 happen-before 对此变量的任意操作(当然也包括写操作了)。

④ happen-before的传递性原则:如果 A 操作 happen-before B 操作,B 操作 happen-before C操作,那么 A 操作happen-before C 操作。

⑤ 线程启动的happen-before原则:同一个线程的 start() 方法 happen-before 于此线程的其它方法。

⑥ 线程中断的happen-before原则:线程 A 新写入的所有变量,当调用 Thread.interrupt(),被打断的线程 B,可以看到 A 的全部操作。

理解:线程 A 写入的所有变量,调用 Thread.interrupt(),被打断的线程 B,可以看到 A 的全部操作。

⑦ 线程终结的happen-before原则:线程中的所有操作都 happen-before 线程的终止检测。

⑧ 对象创建的happen-before原则:一个对象的初始化完成(构造函数执行结束)先于他的 finalize() 方法调用。


相关文章
|
2月前
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【9月更文挑战第37天】在Java的世界里,内存模型是隐藏在代码背后的守护者,它默默地协调着多线程环境下的数据一致性和可见性问题。本文将揭开Java内存模型的神秘面纱,带领读者探索其对并发编程实践的深远影响。通过深入浅出的方式,我们将了解内存模型的基本概念、工作原理以及如何在实际开发中正确应用这些知识,确保程序的正确性和高效性。
|
1月前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
1月前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
40 2
|
2月前
|
缓存 Java 编译器
【多线程-从零开始-伍】volatile关键字和内存可见性问题
【多线程-从零开始-伍】volatile关键字和内存可见性问题
37 0
|
4月前
|
存储 缓存 Java
Java内存模型(JMM)
Java内存模型(JMM)是一个抽象概念,用于规范程序中各种变量(实例字段、静态字段及数组元素)的访问方式,确保不同Java虚拟机(JVM)上的并发程序结果一致可靠。JMM定义了主存储器(所有线程共享)与工作存储器(线程私有)的概念,线程间通过主存储器进行通信。JMM具备三大特性:原子性(确保基本读写操作的不可分割)、可见性(确保一个线程对共享变量的修改对其他线程可见)、有序性(防止指令被处理器或编译器重排序影响程序逻辑)。通过这些特性,JMM解决了多线程环境下的数据一致性问题。
|
4月前
|
安全 Java 程序员
深入浅出Java内存模型:探索JMM的奥秘
在Java编程世界中,理解其内存模型(JMM)是提升代码性能和确保线程安全的关键。本文将带你走进Java内存模型的大门,通过浅显易懂的方式揭示其工作原理,并指导你如何在实际开发中有效利用JMM来避免常见的并发问题。
|
18天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
151 1
|
7天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
16天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
17天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
18 3

热门文章

最新文章