Android---java内存模型与线程

news2024/12/31 5:17:30

Java 内存模型翻译自 Java Memory Model,简称 JMM。它所描述的是多线程并发、CPU 缓存等方面的内容。

在每一个线程中,都会有一块内部的工作内存,这块内存保存了主内存共享数据的拷贝副本。但在 Java 线程中并不存在所谓的工作内存(working memory),它只是对 CPU 寄存器和高速缓存的抽象描述

CPU 普及

线程是 CPU 调度的最小单位,线程中的字节码指令最终都在 CPU 中执行。CPU 在执行的时候,免不了要和各种数据打交道,而 Java 中所有数据都是放在主内存中的。随着 CPU 的发展,CPU 的执行速度越来越快,但内存的技术并没有太大变化,所以在内存中读取和写入数据的过程和 CPU 执行速度比起来,差距会越来越大。如上图箭头所示,CPU 对主内存的访问需要等待较长的时间,这样就体现不出 CPU 超强的运算能力了。

因此为了压榨处理器性能,达到高并发的效果,在 CPU 中添加了高速缓存 cache 来作为缓冲。在执行运算时,CPU 会将运算所使用到的数据复制到高速缓存中,让运算能够快速进行。当运算结束后,再将缓存中的结果刷回主内存。这样 CPU 就不用等待主内存的读写操作了。如下图所示:

 上面的方案,看起来一切正常。但问题也随之而来,每个 CPU 处理器都有自己的高速缓存,同时又共同操作同一块主内存,当多个处理器同时操作主内存时,可能导致数据不一致,这就是缓存一致性问题

 缓存一致性问题

现在,市面上的手机通常有2个或多个 CPU,其中一些 CPU 还有多核。每个 CPU 在某一时刻都能运行一个线程。如果 Java 程序是多线程的,就有可能存在多个线程在同一时刻被不同的 CPU 执行的情况

如下代码所示:

public int x = 0;
public int y = 0;

Thread p1 = new Thread(){
    public void run(){
        int r1 = x;
        y = 1;
    }
};

Thread p2 = new Thread(){
    public void run(){
        int r2 = y;
        x = 2;
    }
};

p1.start();
p2.start();

假设我们的一台设备上有2个CPU,分别为 C1 和 C2,将上面这段代码执行在这台设备上,最后打印出的 r1 和 r2 值分别是多少?答案是不确定的。

情况1:r1 = 0, r2 = 1

假设 P1 现在 C1 中执行完毕,并成功刷新回主内存中,此时 r1 = 0, x = 0, y =1。然后 P2 在 C2 中执行,从主内存中加载 y =1 ,并赋值给 r2。此时 r2 = 1, x = 2, y = 1, r1 = 0。

情况2:r1 = 2, r2 = 0

假设 P2 现在 C1 中执行完毕,并成功刷新回主内存中,此时 r2 = 0, x = 2, y =0。然后 P1 在 C2 中执行,从主内存中加载 x =2 ,并赋值给 r2。此时 r1= 2, x = 2, y = 1, r2 = 0。

情况3:r1 = 0, r2 = 0(特殊)

x, y的值分别缓存在 C1 和 C2 的缓存中,首先 P1 在 C1 中执行完毕,但是并未将结果刷新回主内存中,此时主内存中的 x = 0, y = 0。然后 P2 在 C2 中执行,缓存中的 y = 0, 将其赋值给 r2,此时,r2 = 0 , x = 2, y = 1,如下图所示:

虽然 C1 和 C2 的主内存中修改了 x, y 的值,但并未将它们刷新到主内存中。这就是缓存一致性问题

指令重排

除了缓存一致性问题,还存在另外一种硬件问题,即指令重排。为了使 CPU 内部的运算单元能够尽量充分利用,处理器可能会对输入的字节码指令进行重排序处理,也就是处理器优化。比如 Java 虚拟机的即时编译器(JIT),如以下代码,

a = 1;
b = 2; 
a = a + 1

编译后的字节码指令如下:

可以看出,在上述红框中的指令是表达的同一种语义,并且指令7并不依赖指令2和3,在这种情况下,CPU 会对指令的顺序做如下优化。

从 Java 语言的角度看这种优化如下:

也就是说在 CPU 层面,Java 有时候并不会严格按照文件中的顺序去执行。

缓存一致性问题和指令重排的内容表明,如果我们任由 CPU 优化,那么我们编写的 java 代码所执行的效果会大大出乎我们的意料。

为了解决这个问题,让 Java 代码在不同硬件、不同操作系统中输出的结果达到一致。Java 虚拟机规范提出了一套机制--> java 内存模型

Java 内存模型

内存模型是一套共享内存系统中多线程读写操作行为的规范。这套规范屏蔽了底层各种硬件和操作系统的内存访问差异。解决了 CPU 多级缓存、CPU优化、指令重排等导致的内存访问问题,从而保证 Java 程序(尤其是多线程程序)在各种平台下对内存的访问效果一致。

在 java 内存模型中,统一用工作内存来当作 CPU 中寄存器或高速缓存的抽象。线程之间的共享变量存储在主内存中,每个线程都有一个私有工作内存(类比 CPU 中的寄存器或者高速缓存),本地工作内存中存储了该线程读/写共享变量的副本。

在这套规范中有一个非常重要的规则 happens-before 先行发生原则。

happens-before 先行发生原则

happens-before 用于描述两个操作的内存可见性,通过保证可见性的机制可以让应用程序免于数据竞争干扰。

定义如果一个操作 A happens-before 另一个操作 B,那么操作 A 的执行结果将对操作 B 可见

反过来理解,如果操作 A 的结果必须对操作 B 可见,那么操作 A 必须 happens-before 操作 B。

举例:

private int value = 0;
 
public void setValue(int value){
    value = 1;
}

public int getValue(){
    return value;
}

假设 setValue 就是操作 A,getValue 就是操作 B,如果先后在两个线程中调用 A 和 B,那最后在 B 操作中返回的 value 值是多少呢?

情况1:A happens-before B 不成立

当线程调用操作 B 时,即使操作 A 已经在其它线程中被调用过,并且 value 也被成功设置为1。但这个修改对操作 B 仍然是不可见的,根据上面 CPU 缓存的内容, value 值有可能返回1,也有可能返回0。

情况2:A happens-before B 成立

根据 happens-before 的定义,先行发生动作的结果对后续动作是可见的,也就是先执行 A  后的结果对后续的操作 B 是始终可见的。即,先调用 setValue() 将 value 的值修改为1,后续在其它线程中调用 getValue() 获得的 value 值一定是1。

在 Java 中的两个操作如何算符合 happens-before 规则了呢?

JMM 中定义了以下几种情况是自动符合 happens-before 规则的。

1. 程序次序规则

在单线程内部,如果一段代码的字节码顺序也隐私符合 happens-before 原则,那么逻辑顺序靠前的字节码执行结果一定是对后续逻辑字节码可见。如下代码所示:
 

int a = 10; // 1
b = b + 1; //2

当代码执行到2处时,a = 10 这个结果已经是公之于众的,至于用没用到 a  这个结果则不一定,比如上面的代码中没有用到 a = 10 这个结果。

2. 锁定规则

一个锁如果处于被锁定状态,那么必须先执行 unlock 操作后才能进行 lock 操作。

3. 变量规则

volatile 保证了线程可见性。如果一个线程先写了一个 volatile 变量,然后另一个线程去读这个变量,那么这个写操作一定是 happens-before 读操作的。

4. 线程启动规则

Thread 对象的 start() 方法先行发生于此线程的每一个动作。假定线程 A 在执行过程中,通过执行 ThreadB.start() 来启动线程 B,那么线程 A 对共享变量的修改在线程 B 开始执行后确保对线程 B 可见。

4. 线程中断规则

对线程interrupt()方法的调用先行发生于被中断线程的代码检测,直到中断事件的发生。

5. 线程终结规则

线程中所有的操作都发生在线程的终止检测之前,可以通过Thread.join()方法结束、Thread.isAlive() 的返回值等方法检测线程是否终止执行。假定线程A在执行的过程中,通过调用ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。

6. 对象终结规则

一个对象的初始化完成发生在它的finalize()方法开始前。

happens-before 原则具有传递性

如果操作 A happens-before 操作 B,而操作 B happens-before 操作 C,那么操作 A 一定 happens-before 操作 C。

Java 内存模型应用

根据 happens-before 原则,能够解决在并发环境下操作之间是否可能存在冲突的所有问题。可以可以通过 Java 提供的一系列关键字,将实现的多线程操作“happens-before 化”。happens-before 化”是将本来不符合 happens-before 原则的某些操作,通过某种手段使它们符合 happens-before 原则。

方法1:使用 volatile 关键字

private volatile int value = 0;

public void setValue(int value){
    value = 1;
}

public int getValue(){
    return value;
}

方法2:使用 synchronized 关键字修饰操作

private int value = 0;

public void setValue(int value){
    synchronized{
        value = 1;
    }
}

public int getValue(){
    synchronized{
        return value;
    }
}

通过以上两个方式都可以使 setVaule() 和 getValue() 符合 happens-before 原则。当在某一线程中调用 setVaule()后,再在其它线程中调用 getValue() 获取到的值一定是 1。

总结

Java 内存模型的来源:

主要是因为 CPU 缓存指令重排等优化操作造成多线程程序结果不可控。

Java 内存模型是什么:

本质上它就是一套规范,在这套规范中有一条最重要的 happens-before 原则。

Java 内存模型的使用

简单介绍了两种方式:volatile 和 synchronized。
 

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

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

相关文章

发行版兴趣小组季度动态:Anolis OS 支持大热 AI 软件栈,引入社区合作安全修复流程

发行版兴趣小组(Special Interest Group) :旨在为龙蜥社区构建、发布和维护一个稳定的操作系统发行版。 秋天的季节,发行版兴趣小组在 AI、安全、国产 OS 领域同样也是硕果累累。一起来看一下第三季度发行版兴趣小组的成果总结有…

IPv4报文头部

1、version(版本):用于标识封装是IPv4还是IPv6 2、IHL(头部长度):描述了数据包头的内容长度 3、Type of service(服务类型):用于标识DSCP或IP优先级,用于Qos识别 4、T…

野火开发板使用FlyMcu一键ISP下载时

1.ISP 一键下载 野火开发板使用FlyMcu一键ISP下载时,记得拔掉JTAG那个20针的东西,要不然一直芯片超时不连接。 bsp:9600,使用共写入2KB,进度100%,耗时16641毫秒。 bsp:115200,共写入2KB,进度100%,耗时2188毫秒。 bsp:115200&#…

[modern c++] 函数式编程与 std::ref

参考: std::ref, std::cref - cppreference.comhttps://en.cppreference.com/w/cpp/utility/functional/ref 正文: 如果不涉及函数式编程,那么基本上不需要使用到 std::ref , 这个功能式是用来解决函数式编程时入参只能进行值传…

ai语音机器人OKCC的空号检测

一、空号检测模块介绍 空号检测的原理:空号检测是利用现代通信技术和互联网技术结合而成,采用批量拨电话号码的方式,过滤空号、停机、无效号码。业内又称空号筛选、空号过滤。空号检测技术的成果是去除号码中的无效号码,包括…

代理IP端口是什么意思呢?

今天,咱们来聊聊一个小众但很有料的话题——代理IP端口,它可是你纵横互联网世界的好搭子哦! 首先,我们得先弄明白,代理IP端口是个啥? 代理IP端口就像是通往网络世界的门票,是你和代理服务器之间的桥梁。…

“解锁1688商品详情接口:提升电商转化率的秘密武器!“

1688商品详情接口(又称详情页API)是一种供卖家使用的接口,可以让卖家通过该接口维护商品的详细信息,包括商品价格、图片、属性等等。 通过使用该接口,卖家可以在商品详情页中提供更加详尽、直观的商品描述&#xff0c…

总线带宽计算公式

原文连接:总线带宽计算公式(解析)-

Linux Shell 实现一键部署hfish

hfish前言 HFish是一款社区型免费蜜罐,侧重企业安全场景,从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发,为用户提供可独立操作且实用的功能,通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷感知和威胁情报领域的能…

PromptScript:轻量级 DSL 脚本,加速多样化的 LLM 测试与验证

TL;DR 版本 PromptScript 是一个轻量级的 Prompt 调试用的 DSL (Yaml)脚本,以用于快速使用、构建 Prompt。 PromptScript 文档:https://framework.unitmesh.cc/prompt-script Why PromptScript ? 几个月前&…

JAVA设计模式-装饰者模式

一.概念 装饰器模式(Decorator Pattern),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更灵活。 —-《大话设计模式》 允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属…

多列等高实现

预期效果 多列等高,左右两列高度自适应且一样,分别设置不同背景色效果预览: 分别由6种方法实现 1、使用padding + margin + overflow 实现多列等高效果,具有良好的兼容性; 2、border实现多列等高,左边框宽度为200px,左列浮动,伪元素清除浮动; 3、父元素线性渐变背景色…

Mac热门软件推荐Paste mac 中文激活版 剪切板工具

Paste for Mac是一款运行在Mac OS平台上的剪切板小工具。它拥有华丽的界面效果,并且每一条记录可显示(预览)文本、图片等记录的完整内容。此外,Paste for Mac可以记录最近指定条数的剪切板信息,方便用户随时调用&#…

L15D1 设备分类、设备号申请和注销

一、Linux设备分类 (一)linux的文件种类: -:普通文件:文件IOd:目录文件p:管道文件s:本地socket文件:网络编程l:链接文件c:字符设备b&#xff1a…

18__call__函数的调用

目录 把对象当成函数一样调用 吼吼~补充一个小的知识点哦~就是括号里的参数! 具体的应用场景 原先一个实例化对象,并不会被调用,但是可以引入call class Person:pass pPerson() p() 这个时候引入__call__ 把对象当成函数一样调用 class…

PLC电梯控制系统

目录 PLC电梯控制系统 1电梯简介 1.1电梯的基本分类 1.1.1按用途分类 1.1.2 按驱动系统分类 1.2 电梯的型号 1.3电梯的主要参数及规格尺寸 1.4电梯控制技术 1.5常用交流调速电梯的特点 1.6电梯的工作原理 2 PLC可编程序控制器 2.1 PLC的起源与发展 2.2 PLC控制系统…

视频监控系统/安防视频平台EasyCVR广场视频细节优化

安防视频监控系统/视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同,支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频汇聚平台EasyCVR拓展性强,视频能力丰富,可实现视频监控直播、视频轮播、…

二叉搜索树--查询节点-力扣 700 题

例题细节讲过(二叉搜索树的基础操作-CSDN博客)&#xff0c;下面给出递归实现 public TreeNode searchBST(TreeNode node, int val) {if(node null) {return null;}if(val < node.val) {return searchBST(node.left, val);} else if(node.val < val) {return searchBST(…

Iptabels的相关描述理解防火墙的必读文章

Iptabels是与Linux内核集成的包过滤防火墙系统&#xff0c;几乎所有的linux发行版本都会包含Iptables的功能。如果 Linux 系统连接到因特网或 LAN、服务器或连接 LAN 和因特网的代理服务器&#xff0c; 则Iptables有利于在 Linux 系统上更好地控制 IP 信息包过滤和防火墙配置。…

MATLAB笔记:GUI中的回调函数之间的数据传输

文章目录 效果代码 效果 plot 是画图 hold是hold on show是显示 clear是清除坐标轴 代码 function varargout untitled(varargin) % UNTITLED MATLAB code for untitled.fig % UNTITLED, by itself, creates a new UNTITLED or raises the existing % singleton*.…