Java内存模型

Java内存模型

JMM即java memory model,它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等。

JMM体现在以下几个方面

· 原子性 - 保证指令不受到线程上下文切换的影响(之前的synchornized原理文章有介绍过)

· 可见性 - 保证指令不会受CPU缓存的影响

· 有序性 - 保证指令不会受CPU指令并行优化的影响

可见性

先看一个现象,main线程对run变量的修改对于t线程不可见,导致了t线程无法停止:

@Slf4j(topic = "c.TestDemo")
public class TestDemo {static boolean run = true;public static void main(String[] args) throws InterruptedException {new Thread(()->{while (run){}}).start();Thread.sleep(1000);log.debug("修改run");run = false;}
}

1.初始状态,t线程刚开始从主内存读取了run的值到工作内存。

在这里插入图片描述

2.因为t线程要频繁从主内存中读取run的值,JIT编译器会将run的值缓存至自己工作内存中的高速缓存中,减少对主存中run的访问,提高效率

在这里插入图片描述

3.1秒之后,main线程修改了run的值,并同步至主存,而t是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值

在这里插入图片描述

解决方法

volatile(易变关键字)

它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。

volatile static boolean run = true;
public static void main(String[] args) throws InterruptedException {new Thread(()->{while (run){}}).start();Thread.sleep(1000);log.debug("修改run");run = false;
}
加锁也可以保证可见性
static boolean run = true;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {new Thread(()->{while (run){synchronized (lock){if (!run){break;}}}},"t1").start();Thread.sleep(1000);log.debug("停止t");synchronized (lock){run = false;}
}

但是由于synchronized需要给对象创建monitor比较重量,所以建议使用volatile

可见性&原子性

前一个例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对volatile变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况

在这里插入图片描述

比较一下之前我们将线程安全时举的例子:两个线程一个i++一个i–,只能保证看到最新值,不能解决指令交错

在这里插入图片描述

注意

synchronized语句块既可以保证代码块的原子性,也可以保证代码块的可见性。但缺点是synchronized是属于重量级操作,性能相对较低

如果前边示例死循环中加入System.out.println()不用volatile修饰符,线程t也能正确看到run变量的修改。

有序性

JVM会在不影响正确性的前提下,可以调整语句的执行顺序。

static int i;
static int j;
// 在某个线程内执行如下赋值操作
i = ...;
j = ...;

可以看到,至于是先执行i还是先执行j,对最终的结果不会产生影响。所以,上面代码真正执行时,既可以是先给i赋值也可以是先给j赋值

这种特性称之为指令重排,多线程下指令重排会影响正确性。

指令重排的前提时,重排指令不能影响结果

//可以重排的例子
int a = 10;//指令1
int b = 20;//指令2
System.out.println(a+b);//不能重排的例子
int a = 10;//指令1
int b = a - 5;//指令2

为什么会产生指令重排呢?

事实上,现代处理器会设计为一个时钟周期完成一条执行时间最长的CPU指令。为什么这么做呢?可以想到指令还可以再划分成一个个更小的阶段,例如,每个指令都可以分为:取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 这5个阶段

在这里插入图片描述

术语参考:

· instruction fetch(IF)

· instruction decode(ID)

· execute(EX)

· memory access(MEM)

· register write back(WB)

现代CPU支持多级指令流水线,例如支持同时执行 IF - ID - EX - MEM - WB 的处理器,就可以称为五级指令流水线。这时CPU可以在一个时钟周期内,同时运行五条指令的不同阶段,IPC = 1,本质上,流水线技术并不能缩短单条指令的执行时间,但他变相地提高了指令地吞吐率

在这里插入图片描述

指令重排序在多线程下的影响

int num = 0;
boolean ready = false;//线程1 执行此方法
public void actor1(I_Result r){if(ready){r.r1 = num + num;}else{r.r1 = 1;}
}//线程2 执行此方法
public void actor2(I_Result r){num = 2;ready = true;
}

结果会出现四种情况

情况1: 线程1先执行,这时ready=false,所有进入else分支结果为1

情况2: 线程2限制性num=2,但没来得及执行ready = true,线程1执行,还是进入else分支,结果为1

情况3: 线程二执行到ready=true,线程1执行,结果为4

情况4:线程2执行ready=true,切换到线程1,进入if分支,相加为0,再切回线程2执行num=2

情况4这种情况就是指令重排的体现

这里只需在ready 变量前加上volatile关键字就行了(不在num属性上加是因为volatile关键字能确保之前的代码也不指令重排 )

happens-before

happens-before规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套总结,抛开以下happens-before规则,JMM并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见

· 线程解锁m之前对变量的写,对于接下来对m加锁的其他线程对该变量的读可见

static int x;
static Object m = new Object();
new Thread(()->{synchronized(m){x = 10;}
},"t1").start();
new Thread(()->{synchronized(m){System.out.println(x);}
},"t2").start();

· 线程对volatile变量的写,对接下来其它线程对该变量的读可见

static int x;
static Object m = new Object();
new Thread(()->{synchronized(m){x = 10;}
},"t1").start();
new Thread(()->{synchronized(m){System.out.println(x);}
},"t2").start();

· 线程对volatile变量的写,对接下来其他线程对该变量的读可见

volatile static int x;
new Thread(()->{x = 10;
},"t1").start();
new Thread(()->{System.out.println(x);
},"t2").start();

· 线程start前对变量的写,对该线程开始后对该变量的读可见

static int x;
x = 10;
new Thread(()->{System.out.println(x);
},"t2").start();

· 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用t1.isAlive()或t1.join()等待它结束)

static int x;
Thread t1 = new Thread(()->{x = 10;
},"t1");
t1.start();
t1.join();
System.out.println(x);

· 线程t1打断t2(interrupt)前对变量的写,对于其他线程得知t2被打断后对变量的读可见(通过t2.interrupted或t2.isInterrupted)

static int x;
public static void main(String[] args){Thread t2 = new Thread(()->{while(true){if(Thread.currentThread().isInterrupted()){System.out.println(x);break;}}},"t2");t2.start();new Thread(()->{sleep(1);x = 10;t2.interrupt();},"t1").start();while(!t2.isInterrupted()){Thread.yield();}System.out.println(x);
}

· 对变量默认值(0,false,null)的写,对其它线程对该变量的读可见。具有传递性,如果x hb-> y并且y hb-> z 那么有x hb-> z,配合volatile的防指令重排。

volatile static int x;
static int y;
new Thread(()->{y = 10;x = 20;
},"t1").start();
new Thread(()->{// x=20 对 t2可见,同时 y=10 也对t2可见System.out.println(x);
},"t2").start();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/4970.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

软考高级架构 - 8.2 - 系统架构评估 - 超详细讲解+精简总结

8.2-系统架构评估 系统架构评估就是对系统架构的质量进行分析,以便帮助设计者作出架构决策,确保系统能够符合需求。评估方法大致分为三种: 基于问卷或检查表:通过设计好的问卷或清单,收集开发人员和相关人员的…

【LLM】Generative Agents和代码实践

note Generative Agents是2023年斯坦福提出的agent小镇,通过memory->reflection->planning的框架提高NPC的行为目标性,给游戏NPC的灵活设计带来了可能。Generative Agents是一种多智能体交互的框架,它模拟现实中的人类行为。这些Agent…

K. Farm Management 【CCPC2024哈尔滨站】

K. Farm Management 思路: 很容易想到的策略: 给每个作物都安排最短的时长 l l l,剩下的时间作为可支配时间。选择删除收益最高的作物,然后将尽可能多的可支配时间用于这个作物。或者选择删除收益低时间还长的作物,然后将时间解…

智能 ODN 系统研究与设计

智能 ODN 系统研究与设计 摘 要:为了解决ODN面临的光纤错综复杂,故障定位低效等问题,引入电子标签,智能OTDR技术,提出了一种基于电子标签、光纤检测笔和智能OTDR故障监测的智能 ODN 解决方案,并重点讲述了智能ODN系统的…

Openlayers实现长度测量

概述 在 Openlayers 中,计算两点之间的距离,通常会用到ol/sphere模块。ol/sphere模块主要用于处理与球体(特别是地球球体)相关的数学和几何计算。而长度测量主要用到ol/sphere中的getDistance函数。 getDistance函数用于计算地球表面两点之间的距离,通常用于经纬度坐标。…

xftp连接中不成功 + sudo vim 修改sshd_config不成功的解决方法

我们使用sudo vim不成功,但是我们使用sudo su就可以 了! root用户权利更大! 喵的,终于成功了,一个xftp连接半天不成功。(添加上面的内容就可以连接成功了↑)

常用的c++特性-->day02

可调用对象 案例 #include <iostream> #include <string> #include <vector> using namespace std;using funcptr void(*)(int, string);int print(int a, double b) {cout << a << ", " << b << endl;return 0; } // …

应急车道占用检测算法的技术方案与应用

应急车道是高速公路上的生命通道&#xff0c;专门用于救护车、消防车、警车等紧急车辆通行&#xff0c;帮助应对突发状况。然而&#xff0c;一些驾驶员出于各种原因违规占用应急车道&#xff0c;阻碍救援车辆的正常通行&#xff0c;导致交通救援效率大幅下降&#xff0c;甚至加…

卡尔曼滤波算法Kalman filter algorithm

一、假如 状态向量服从高斯分布&#xff1a; 而且状态转移是线性的&#xff1a; 测量是状态的线性关系&#xff08;带噪声&#xff09; 初始状态的置信度也是正态分布 二、卡尔曼滤波的算法流程为 疑问&#xff1a;多测量融合&#xff0c;是不是用不同的传感器测量值去重复5…

java_继承

1.为啥用 继承? Pupil类 package com.hspedu.extend;// 小学生->模拟小学生考试的情况 public class Pupil {public String name;public int age;private double score;public void setScore(double score) {this.score score;}public void testing() {System.out.printl…

Redis 缓存击穿

目录 缓存击穿 什么是缓存击穿&#xff1f; 有哪些解决办法&#xff1f; 缓存穿透和缓存击穿有什么区别&#xff1f; 缓存雪崩 什么是缓存雪崩&#xff1f; 有哪些解决办法&#xff1f; 缓存预热如何实现&#xff1f; 缓存雪崩和缓存击穿有什么区别&#xff1f; 如何保…

【Redis的安装以及主从复制的搭建】配置Redis的哨兵模式

文章目录 一、安装Redis1、上传、解压、重命名2、安装GCC环境3、编译源码4、进行安装5、修改配置文件redis.conf 二、Redis主从复制搭建1、创建文件夹用来实现主从复制 三、配置Redis的哨兵模式1、创建文件夹2、修改sentinel.conf配置文件3、启动 一、安装Redis 1、上传、解压…

WPF中的转换器

单值转换器 1.创建项目后下载两个NuGet程序包 2.先定义一个转换器实现IValueConverter接口 using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; usin…

GEE Ui——批量查询Sentinel-2 图像无云区域的可视化应用(提供缩略图)

目录 简介 功能选项 函数 Map.clear() No arguments. Returns: ui.Map Map.onClick(callback) Arguments: Returns: String reduceRegion(reducer, geometry, scale, crs, crsTransform, bestEffort, maxPixels, tileScale) Arguments: Returns: Dictionary toLis…

汇编练习-1

1、要求 练习要求引自《汇编语言-第4版》实验10.3(P209页) -编程&#xff0c;将data段中的数据&#xff0c;以10进制的形式显示出来 data segment dw 123,12666,1,8,3,38 data ends 2、实现代码(可惜没找到csdn对8086汇编显示方式) assume cs:codedata segmentdw 16 dup(0) ;除…

xml格式转为txt格式?

数据集的标签格式为xml格式&#xff0c;转为yolo的训练格式&#xff1a; 1.创建一个格式转化的.py文件将下面代码复制&#xff1a; import os import glob import xml.etree.ElementTree as ETdef get_classes(classes_path):with open(classes_path, encodingutf-8) as f:cl…

M - Weird Ceiling 【CCPC2024哈尔滨站】

M - Weird Ceiling 思路: 注意到 f ( n , i ) f(n,i) f(n,i) 的值为 n y ( i ) \frac{n} {y(i)} y(i)n​ &#xff0c;其中 y ( i ) y(i) y(i) 为 n n n 小于等于 i i i 的最大因数。 那么先找到 n n n的所有因数&#xff0c;包括 1 1 1和它本身&#xff0c;在数组a[]中升…

机器学习与数学公式

目录 在机器学习中&#xff0c;将公式应用到算法程序上主要涉及以下几个步骤&#xff1a; 1、数学公式转换成编程逻辑&#xff1a; 2、选择合适的编程语言和工具&#xff1a; 3、使用矩阵运算和优化方法&#xff1a; 4、实现算法逻辑&#xff1a; 5、将公式封装成函数…

基于微信小程序的校园失物招领系统的研究与实现(V4.0)

博主介绍&#xff1a;✌stormjun、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

芯突破 | 国产PCIe桥接芯片掀起的连接革命

PCIe桥接芯片应运而生 随着计算机技术的快速发展&#xff0c;硬件性能不断提升&#xff0c;数据传输速度的需求日益增长&#xff0c;PCIe总线凭借其高带宽和低延迟的优势&#xff0c;已经成为主流。然而&#xff0c;由于不同设备和PCIe总线之间的接口差异&#xff0c;直接连接…