当前位置: 首页 > news >正文

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

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。
 

相关文章:

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

Java 内存模型翻译自 Java Memory Model,简称 JMM。它所描述的是多线程并发、CPU 缓存等方面的内容。 在每一个线程中,都会有一块内部的工作内存,这块内存保存了主内存共享数据的拷贝副本。但在 Java 线程中并不存在所谓的工作内存&#xff0…...

23.10.7.sql 里面的DISTINCT

例如: SELECT DISTINCT t.container_no FROM biz_inventory_task_detail t 这里distinct干嘛的 解释: DISTINCT是一个关键字,用于在SELECT语句中返回唯一不重复的值。 在这个查询中,使用DISTINCT关键字,是为了返回biz…...

mysql面试题38:count(1)、count(*) 与 count(列名) 的区别

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官: count(1)、count(*) 与 count(列名) 的区别 当使用COUNT函数进行数据统计时&…...

nodejs+vue+elementui大学生心理健康管理系统

简单的说 Node.js 就是运行在服务端的 JavaScript。 前端技术:nodejsvueelementui 前端:HTML5,CSS3、JavaScript、VUE本大学生心理健康管理系统使用简洁的框架结构,专门用于用户咨询心理专家,系统具有方便性、灵活性、应用性。于是…...

【MySQL】深入解析MySQL双写缓冲区

原创不易,注重版权。转载请注明原作者和原文链接 文章目录 为什么需要Doublewrite BufferDoublewrite Buffer原理Doublewrite Buffer和redo logDoublewrite Buffer相关参数总结 在数据库系统的世界中,保障数据的完整性和稳定性是至关重要的任务。为了实现…...

u-boot 编译与运行

文章目录 u-boot 编译与运行环境配置ubuntu 版本qemu 版本u-boot 版本(master)交叉工具链版本 u-boot 源码下载生成配置文件报错情况一报错情况2 u-boot 配置编译编译脚本编译报错解决编译日志编译产物 运行 u-boot 编译与运行 本文主要介绍 u-boot 编译…...

C++QT-day2

#include <bits/stdc.h>/*自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c;定义公有成员函数:初始化函数:void init(int w, int h)更改宽度的函数:set_w(int w)更改高度的函数:set_h(int h)输出该矩形的周长和面积函数:void sho…...

【Acwing187】导弹防御系统(LIS+剪枝+贪心+dfs+迭代加深)

题目描述 看本文需要准备的知识 1.最长上升子序列&#xff08;lis&#xff09;的算法思想和算法模板 2.acwing1010拦截导弹&#xff08;lis贪心&#xff09;题解 本题题解&#xff0c;需要知道这种贪心算法 3.简单了解dfs暴力搜索、剪枝、搜索树等概念 思路讲解 dfs求最…...

字节大佬带你五分钟掌握接口自动化测试框架

今天&#xff0c;我们来聊聊接口自动化测试是什么&#xff1f;如何开始&#xff1f;接口自动化测试框架怎么做&#xff1f; 自动化测试 自动化测试&#xff0c;这几年行业内的热词&#xff0c;也是测试人员进阶的必备技能&#xff0c;更是软件测试未来发展的趋势。 特别是在…...

上传文件夹里面的文件后,按树结构的table表格展示

1. 先处理最简单的 原始数据大概是这样: let fileArr [{progress: 100,status: 成功,type: 通号,webkitRelativePath: "六捷数据2023-05-04 163909/G163/Abis口详细信息_(G163)(380BL3544-0)(14984173988)(2018-01-24 174431.0740—2018-01-24 180347.9070).xls"…...

【error】root - Exception during pool initialization

报错提示&#xff1a;root - Exception during pool initialization. 错误原因&#xff1a; 配置数据库出错 我的错误配置&#xff1a; spring.datasource.urljdbc:mysql://localhost:3306/springboot?serverTimezoneGMT spring.datasource.nameroot spring.datasource.pass…...

【重拾C语言】九、再论函数(指针、数组、结构体作参数;函数值返回指针、结构体;作用域)

目录 前言 九、再论函数 9.1 参数 9.1.1 参数的传递规则 9.1.2 指针作参数 9.1.3 数组作参数 9.1.4 结构体作参数 a. 直接用结构体变量作函数参数 b. 用指向结构体变量的指针作函数参数 9.2 函数值 9.2.1 返回指针值 9.2.2 返回结构体值 a. 返回结构体值 b. 返回…...

Spring WebClient 基于响应式编程模型的HTTP客户端

一、简介 WebClient是一个非阻塞的、可扩展的、基于Reactive Streams规范的HTTP客户端。它提供了一种简洁的方式来进行HTTP请求&#xff0c;并且可以很好地与其他Spring组件集成。WebClient支持同步和异步操作&#xff0c;使得它非常适合用于构建响应式应用程序。 WebClient允…...

IP真人识别方法与代理IP检测技术

随着互联网的发展&#xff0c;IP地址在网络安全和数据分析中扮演着重要的角色。为了维护网络的安全性和识别真实用户&#xff0c;IP地址的真实性和来源成为了一个关键问题。 什么是IP真人识别&#xff1f; IP真人识别是一种技术&#xff0c;旨在确定IP地址背后的用户是否为真实…...

MySQL 面试知识脑图 初高级知识点

脑图下载地址&#xff1a;https://mm.edrawsoft.cn/mobile-share/index.html?uuid18b10870122586-src&share_type1 sql_mode 基本语法及校验规则 ONLY_FULL_GROUP_BY 对于GROUP BY聚合操作&#xff0c;如果在SELECT中的列&#xff0c;没有在GROUP BY中出现&#xff…...

【数据结构】二叉树的链式结构及实现

目录 1. 前置说明 2. 二叉树的遍历 2.1 前序、中序以及后序遍历 2.2 层序遍历 3. 节点个数及高度等 4. 二叉树的创建和销毁 1. 前置说明 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。由于现在大家对二叉树结构…...

OpenCV4(C++)—— 创建窗口滑动条来调参

文章目录 创建滑动条 —— createTrackbar 创建滑动条 —— createTrackbar createTrackbar是OpenCV中的一个函数&#xff0c;用于创建一个可调节的滑动条&#xff08;Trackbar&#xff09;&#xff0c;以便在图像处理过程中实时调整参数 int cv::createTrackbar(const String…...

深度学习基础知识 学习率调度器的用法解析

深度学习基础知识 学习率调度器的用法解析 1、自定义学习率调度器**&#xff1a;**torch.optim.lr_scheduler.LambdaLR2、正儿八经的模型搭建流程以及学习率调度器的使用设置 1、自定义学习率调度器**&#xff1a;**torch.optim.lr_scheduler.LambdaLR 实验代码&#xff1a; i…...

【JUC系列-12】深入理解PriorityQueue的底层原理和基本使用

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…...

Paddle安装

Paddle安装参考 docs/tutorials/INSTALL_cn.md PaddlePaddle/PaddleDetection - Gitee.comhttps://gitee.com/paddlepaddle/PaddleDetection/blob/release/2.6/docs/tutorials/INSTALL_cn.md # 不指定版本安装paddle-gpu python -m pip install paddlepaddle-gpu# 测试安装 …...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...