【3】Java内存模型

article/2025/10/5 16:54:18

目录

 

知识点1:Java内存模型

知识点2:Volatile

1、什么是Volatile

2、Volatile特性

3、Volatile与Synchronized区别

知识点3:重排序

1、数据依赖性

2、as-if-serial语义

3、程序顺序规则

4、重排序对多线程的影响


知识点1:Java内存模型

共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

                                                           

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

下面通过示意图来说明这两个步骤:     

                                                             

如上图所示,本地内存AB有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1

从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。


知识点2:Volatile

1、什么是Volatile

可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存。

 

Volatile 保证了线程间共享变量的及时可见性,但不能保证原子性

class ThreadVolatileDemo extends Thread {public    boolean flag = true;@Overridepublic void run() {System.out.println("开始执行子线程....");while (flag) {}System.out.println("线程停止");}public void setRuning(boolean flag) {this.flag = flag;}}public class ThreadVolatile {public static void main(String[] args) throws InterruptedException {ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();threadVolatileDemo.start();Thread.sleep(3000);threadVolatileDemo.setRuning(false);System.out.println("flag 已经设置成false");Thread.sleep(1000);System.out.println(threadVolatileDemo.flag);}
}

  运行结果:

 

已经将结果设置为fasle为什么?还一直在运行呢。

原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。

解决办法使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值

2、Volatile特性

1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

 

volatile 性能:

  volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

3、Volatile与Synchronized区别

(1)从而我们可以看出volatile虽然具有可见性但是并不能保证原子性。

(2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。

但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。


知识点3:重排序

1、数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:

名称

代码示例

说明

写后读

a = 1;b = a;

写一个变量之后,再读这个位置。

写后写

a = 1;a = 2;

写一个变量之后,再写这个变量。

读后写

a = b;b = 1;

读一个变量之后,再写这个变量。

 

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。

前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

2、as-if-serial语义

s-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。

为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。为了具体说明,请看下面计算圆面积的代码示例:

double pi  = 3.14;    //A

double r   = 1.0;     //B

double area = pi * r * r; //C

上面三个操作的数据依赖关系如下图所示:

                                                             https://res.infoq.com/articles/java-memory-model-2/zh/resources/11.png

如上图所示,A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。因此在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序。下图是该程序的两种执行顺序:

                                                             https://res.infoq.com/articles/java-memory-model-2/zh/resources/22.png

as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

3、程序顺序规则

根据happens- before的程序顺序规则,上面计算圆的面积的示例代码存在三个happens- before关系:

1.     A happens- before B;

2.     B happens- before C;

3.     A happens- before C;

这里的第3个happens- before关系,是根据happens- before的传递性推导出来的。

这里A happens- before B,但实际执行时B却可以排在A之前执行(看上面的重排序后的执行顺序)。在第一章提到过,如果A happens- before B,JMM并不要求A一定要在B之前执行。JMM仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。这里操作A的执行结果不需要对操作B可见;而且重排序操作A和操作B后的执行结果,与操作A和操作B按happens- before顺序执行的结果一致。在这种情况下,JMM会认为这种重排序并不非法(not illegal),JMM允许这种重排序。

在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽可能的开发并行度。编译器和处理器遵从这一目标,从happens- before的定义我们可以看出,JMM同样遵从这一目标。

4、重排序对多线程的影响

现在让我们来看看,重排序是否会改变多线程程序的执行结果。请看下面的示例代码:

class ReorderExample {
int a = 0;
boolean flag = false;public void writer() {a = 1;                   //1flag = true;             //2
}Public void reader() {if (flag) {                //3int i =  a * a;        //4……}
}
}

flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程ABA首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入?

答案是:不一定能看到。

由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作1和操作2重排序时,可能会产生什么效果?请看下面的程序执行时序图:

                                                             https://res.infoq.com/articles/java-memory-model-2/zh/resources/33.png

如上图所示,操作1和操作2做了重排序。程序执行时,线程A首先写标记变量flag,随后线程B读这个变量。由于条件判断为真,线程B将读取变量a。此时,变量a还根本没有被线程A写入,在这里多线程程序的语义被重排序破坏了!

注:本文统一用红色的虚箭线表示错误的读操作,用绿色的虚箭线表示正确的读操作。

下面再让我们看看,当操作3和操作4重排序时会产生什么效果(借助这个重排序,可以顺便说明控制依赖性)。下面是操作3和操作4重排序后,程序的执行时序图:

                                                            https://res.infoq.com/articles/java-memory-model-2/zh/resources/44.png

在程序中,操作3和操作4存在控制依赖关系。当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜测(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓存中。当接下来操作3的条件判断为真时,就把该计算结果写入变量i中。

从图中我们可以看出,猜测执行实质上对操作34做了重排序。重排序在这里破坏了多线程程序的语义!

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。


http://chatgpt.dhexx.cn/article/58s7GSBp.shtml

相关文章

Java内存模型详解

文章目录 一、什么是JMM?为什么需要JMM?二、JMM 如何抽象线程和主内存之间的关系?三、Java 内存区域和 JMM 有何区别?四、happens-before原则五、总结 一、什么是JMM?为什么需要JMM? Java 是最早尝试提供内…

java内存模型概述

java内存模型 为了控制线程之间的通信,(完成底层封装) 用来屏蔽掉各种硬件和操作系统之间的内存访问差异,以实现让Java程序在各平台下都能达到一致的内存访问效果。 JMM目标:定义程序中各个变量的访问规则&#xff0c…

理解Java内存模型(JMM)

本篇的写作思路是先阐明Java内存区域划分、硬件内存架构、Java多线程的实现原理与Java内存模型的具体关系,在弄明白它们间的关系后,进一步分析Java内存模型作用以及一些实现手段 理解Java内存区域与Java内存模型 Java内存区域 Java虚拟机在运行程序时会…

Java 内存模型

Java 内存模型(Java Memory Model),简称 JMM。 JVM 中试图定义一种 JMM 来屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。 一、物理内存模型 物理机遇到的并发问题与虚拟机中…

Java内存模型

Java内存模型 1.内存模型概念图2.线程共享区方法区常量池运行时常量池全局字符串池class文件常量池 堆空间3.线程独占区虚拟机栈本地方法栈程序计数器 4.对象的创建 1.内存模型概念图 2.线程共享区 方法区 方法区也是线程共享区用于储存虚拟机加载的类信息(类的版…

一文看懂Java内存模型(JMM)

文章目录 Java内存模型介绍总览图Java内存模型图线程、主内存、工作内存关系图CPU缓存架构图Java内存模型与硬件内存架构的关系 什么是Java内存模型Java内存模型的意义Java内存模型规范 Java内存模型的主要结构1、程序计数器(Program Counter Register)2…

SPSS建立时间序列模型

原始数据可能并不是时间序列,因此,对这样的数据建立时间序列模型,分为以下几步: 1 生成时间序列:定义新的时间变量,并对原始数据进行平稳处理 a)定义时间变量: 选择按什么时间序列进行定义&am…

时间序列模型(ARIMA和ARMA)完整步骤详述

我于2019年发布此篇文章至今收获了许多人的指点,当时的代码的确晦涩难懂,近期有空,将代码重新整理了一遍,重新发送至此。希望能够帮助大家更好地理解。 建模步骤: 目录 数据包和版本申明 步骤一:数据准备…

机器学习之时间序列模型

一、时间序列概念 在生产和科学研究中,对某一个或一组变量x(t)进行观察测量,将在一系列时刻t1, t2, …, tn (t为自变量)按照时间次序排列,并用于解释变量和相互关系的数学表达式。在相等的时间间隔内收集到的不同时间点的数据集合我们称之为时…

数学建模学习笔记(十)——时间序列模型

文章目录 一、时间序列综述二、时间序列数据以及基本概念三、时间序列分解四、指数平滑模型五、一元时间序列分析的模型六、AR(p)模型七、MA(q)模型八、ARMA(p, q)模型九、模型选择:AIC 和 BIC …

时间序列模型步骤教程(ARIMA)

目录 0、前言一、数据准备&探索1、平稳性1.1 平稳性检验1.2 数据处理(平滑、变换、差分、分解)1.2.1 对数变换1.2.2 平滑法(移动平均&指数平均)1.2.3 差分法1.2.4 分解 2、非白噪声检验 二、模型(ARIMA&#x…

时间序列模型分析

目录 一个引言 定义 确定性时间序列分析方法概述 确定性时间序列模型类型 移动平均法 简单移动平均法 加权移动平均法 趋势移动平均法 指数平滑法 一次指数平滑法 1.预测模型​编辑 2.加权系数的选择​编辑 3.初始值的确定​编…

时间序列模型相关说明和模型介绍

一、什么是时间序列? 时间序列是在规律性时间间隔记录的观测值序列。依赖于观测值的频率,典型的时间序列可分为每小时、每天、每周、每月、每季度和每年为单位记录。 import pandas as pd df pd.read_csv(https://raw.githubusercontent.com/selva86/datasets/d…

常用的时间序列模型

白噪声模型 时间序列算法之ARIMA模型 对非平稳时间序列的分析方法可以分为确定性因素分解的时序分析和随机时序分析两个大类。 确定性因素分解的方法把所有序列的变化都归结为4个因素:长期趋势、季节变动、循环变动和随机波动。其中长期趋势和季节变动的规律性信息通…

数学建模之时间序列模型及其应用

摘要 时间序列模型就是将预测对象按照时间顺序排列起来,用这一组时间序列过去的变化规律,推断今后变化的可能性及变化趋势、变化规律。 时间序列模型也是一种回归模型,其一方面承认事物发展的延续性,运用过去的数据来推测事物的发展趋势;另一方面又考虑到偶然因素产生的随机性…

集成时间序列模型提高预测精度

使用Catboost从RNN、ARIMA和Prophet模型中提取信号进行预测 集成各种弱学习器可以提高预测精度,但是如果我们的模型已经很强大了,集成学习往往也能够起到锦上添花的作用。流行的机器学习库scikit-learn提供了一个StackingRegressor,可以用于…

时间序列规则和时间序列模型

1. 时间序列规则 1.1 什么是时间序列规则 对于赛题/业务的规则之前已经描述了它的重要性和应用,在此不再赘述。这章主要了解时间序列及其规则,和周期的应用。 1.1.1 时间序列 时间序列(或称动态数列):指将同一统计…

时间序列模型算法 - Prophet,LSTM(二)

时间序列模型 - Prophet 1.时间序列简介1.1 时间序列 - 平稳性检验1.1.1 log法1.1.2 差分法 1.2 平稳性的单位根检验 2.ARIMA3.Prophet3.1 Prophet的优点3.2 安装Prophet3.3 数据处理3.4 预测 4.LSTM4.1 数据处理4.2 训练预测 1.时间序列简介 在做时间序列时,首先要…

时间序列模型-ARIMA

一、ARIMA模型基本概念 1.1 自回归模型(AR) 描述当前值与历史值之间的关系,用变量自身的历史数据对自身进行预测;自回归模型必须满足平稳性的要求;(何为平稳性:见时间序列数据分析基本概念&am…

ARMA 时间序列模型

更好的理解协方差以及相关系数 ###X因素和Y因素协方差公式: 自相关系数ACF 直观上来说,ACF 描述了一个观测值和另一个观测值之间的自相关,包括直接和间接的相关性信息。 其实自相关系数可以这么理解:把一列数据按照滞后数拆成两…