JMM初学
文章目录
- 1,线程间的同步和通信
- 1.1, 共享内存并发模型 (Shared Memory Model)
- 线程通信机制
- 线程同步机制
- 特点
- 1.2, 消息传递并发模型 (Message Passing Model)
- 线程通信机制
- 线程同步机制
- 特点
- 适用场景对比
- 2,Java内存模型JMM
- 2.0,Java内存模型的基础
- (1)内存屏障
- (2)happens before
- 2.1,内存可见性
- (1)为什么会出现内存可见性问题?
- (2)内存可见性的发生过程
- (3)JMM如何保证内存可见性
- 2.2,JMM与重排序
- (1)指令重排序的类型
- (2)JMM如何限制重排序
- 2.3顺序一致性模型
- (1)核心定义
- (2)同步程序的顺序一致性效果
- 根据 JMM 的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主存中读取。
- Java 运行时内存区域和JMM
- Java 运行时内存区域描述的是在 JVM 运行时,如何将内存划分为不同的区域,并且每个区域的功能和工作机制。
- Java 内存模型 (JMM) 主要针对的是多线程环境下,如何在主内存与工作内存之间安全地执行操作。它涵盖的主题包括变量的可见性、指令重排、原子操作等,旨在解决由于多线程并发编程带来的一些问题。(可见性,有序性,原子性)
- 指令重排是为了提高 CPU 性能,但是可能会导致一些问题,比如多线程环境下的内存可见性问题。
1,线程间的同步和通信
并发编程的线程之间存在两个问题:
-
线程间如何通信?即:线程之间以何种机制来交换信息
-
线程间如何同步?即:线程以何种机制来控制不同线程间发生的相对顺序
有两种并发模型可以解决这两个问题:
- 消息传递并发模型
- 共享内存并发模型
1.1, 共享内存并发模型 (Shared Memory Model)
Java主要采用这种模型
线程通信机制
- 通过共享内存进行通信
- 线程之间共享程序的公共状态(变量、对象等)
- 线程通过读写共享内存中的变量来隐式通信
线程同步机制
- 使用显式同步原语控制执行顺序
- 主要同步手段:
- 锁(synchronized, Lock)
- volatile变量
- 原子变量(AtomicInteger等)
- 内存屏障
特点
- 通信是隐式的(通过内存访问)
- 需要程序员显式控制同步
- 容易出现竞态条件、死锁等问题
1.2, 消息传递并发模型 (Message Passing Model)
如Go语言的channel、Actor模型
线程通信机制
- 通过发送和接收消息进行显式通信
- 线程/进程间没有共享状态
- 消息通道是唯一的通信媒介
线程同步机制
- 通信本身就是同步的(发送和接收操作)
- 常见实现方式:
- 同步消息传递(发送者阻塞直到消息被接收)
- 异步消息传递+消息队列
- CSP(Communicating Sequential Processes)模型
特点
- 通信是显式的(明确的send/receive操作)
- 同步内建于通信机制中
- 避免了共享内存带来的许多问题
适用场景对比
场景 | 推荐模型 | 原因 |
---|---|---|
分布式系统 | 消息传递 | 天然适合网络通信 |
单机高并发 | 共享内存 | 性能更高 |
简单并发任务 | 消息传递 | 更易实现和维护 |
复杂数据共享 | 共享内存 | 更高效的数据访问 |
容错系统 | 消息传递 | 更好的隔离性和恢复能力 |
实时系统 | 共享内存 | 更低延迟 |
2,Java内存模型JMM
Java内存模型(Java Memory Model, JMM)是Java虚拟机规范中定义的一种内存访问规范,它是一种抽象概念,包含缓存、写缓冲区、寄存器等。它规定了多线程环境下如何正确地访问共享变量,以及线程之间如何通过内存进行通信。即解决上述的“线程间如何通信”和“线程间如何同步两个问题”。保证多线程环境下的可见性、有序性和原子性。
JMM解决的三大问题
问题类型 | 描述 | JMM解决方案 |
---|---|---|
可见性 | 一个线程修改共享变量后其他线程立即可见 | volatile、synchronized、final |
有序性 | 指令执行顺序与代码顺序一致 | happens-before、内存屏障 |
原子性 | 操作不可中断 | synchronized、原子类 |
2.0,Java内存模型的基础
(1)内存屏障
屏障类型 | 作用 |
---|---|
LoadLoad | 禁止 Load1 和 Load2 重排序 |
StoreStore | 禁止 Store1 和 Store2 重排序 |
LoadStore | 禁止 Load 和后续 Store 重排序 |
StoreLoad | 禁止 Store 和后续 Load 重排序 |
eg:LoadLoad屏障
确保 Load1
先于 Load2
执行,防止读操作重排序。
StoreLoad 屏障(全能屏障)
- 作用:
- 禁止
Store
和后续Load
重排序。 - 强制刷新所有写操作到主内存,并 使其他 CPU 缓存失效。
- 禁止
- 开销最大,但能保证最强的内存一致性。‘
(2)happens before
Happens-Before 是 Java 内存模型(JMM)的核心概念,它定义了多线程环境下操作之间的可见性保证和执行顺序约束,使开发者能够在不深入理解底层内存屏障的情况下编写正确的并发程序。
Happens-Before 描述的是两个操作之间的偏序关系:
- 如果操作 A happens-before 操作 B,那么:
- A 的执行结果对 B 可见
- A 的代码顺序在 B 之前
📌 注意:Happens-Before 并不一定代表时间上的先后,而是可见性保证!
happens-before的六大规则
- 程序顺序规则:同一线程中的操作,按照代码顺序 happens-before。
- 监视器锁规则:解锁(unlock) happens-before 后续的加锁(lock)。
- volatile 变量规则:volatile 写 happens-before 后续的 volatile 读。
- 线程启动规则:
Thread.start()
happens-before 该线程的所有操作。 - 线程终止规则:线程的所有操作 happens-before 其他线程检测到它终止(如
t.join()
)。 - 传递性规则:如果 A happens-before B,且 B happens-before C,则 A happens-before C。
2.1,内存可见性
内存可见性(Memory Visibility)是多线程编程中的一个核心概念,指的是当一个线程修改了共享变量的值后,其他线程能否立即看到这个修改。如果修改后的值不能及时被其他线程观察到,就会导致内存可见性问题,从而引发程序逻辑错误。
什么是共享变量共享变量是指在多线程环境下可以被多个线程共同访问和修改的变量。对于每一个线程来说,栈都是私有的,而堆是共有的。也就是说,在栈中的变量(局部变量、方法定义的参数、异常处理的参数)不会在线程之间共享,也就不会有内存可见性的问题,也不受内存模型的影响。而在堆中的变量是共享的,一般称之为共享变量。所以,内存可见性针对的是堆中的共享变量。
(1)为什么会出现内存可见性问题?
现代计算机和 JVM 为了提高性能,会采用以下优化策略,导致内存可见性问题:
(1) CPU 缓存架构
- CPU 不会直接读写主内存(RAM),而是通过**多级缓存(L1/L2/L3 Cache)**来提高访问速度。
- 每个 CPU 核心有自己的缓存,线程运行时可能只更新自己的缓存,而不会立即同步到主内存。
- 因此,一个线程的修改可能对其他线程不可见。
(2) 指令重排序(Reordering)
-
编译器优化:JIT 编译器可能会调整指令顺序以提高性能。
-
CPU 乱序执行:CPU 可能会改变指令的执行顺序(只要不影响单线程语义)。
-
这可能导致线程 A 的修改操作被延迟或乱序执行,导致线程 B 看到的数据不一致。
public class ReorderingProblem {
private static int x = 0;
private static int y = 0;
private static boolean ready = false;public static void main(String[] args) {Thread writer = new Thread(() -> {x = 1;y = 2;ready = true; // 可能被重排序到前面});Thread reader = new Thread(() -> {while (!ready); // 等待ready=trueSystem.out.println("x=" + x + ", y=" + y); // 可能输出x=0, y=2});writer.start();reader.start(); }
}
由于指令重排序问题,可能会ready = true
可能先于 x = 1
执行,导致 reader
线程看到 x=0
,但 y=2
。
(3) 工作内存(Working Memory)抽象
- JMM(Java 内存模型)规定,每个线程有自己的工作内存(可以理解为 CPU 缓存 + 寄存器 + 写缓冲区)。
- 线程操作共享变量时,先在工作内存中修改,再同步回主内存,这可能导致其他线程看不到最新值。
(2)内存可见性的发生过程
从图中可以看出:
- 所有的共享变量都存在主存中。
- 每个线程都保存了一份该线程使用到的共享变量的副本。
- 如果线程 A 与线程 B 之间要通信的话,必须经历下面 2 个步骤:
- 线程 A 将本地内存 A 中更新过的共享变量刷新到主存中去。
- 线程 B 到主存中去读取线程 A 之前已经更新过的共享变量。
所以,线程 A 无法直接访问线程 B 的工作内存,线程间通信必须经过主存。
注意,根据 JMM 的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主存中读取。
所以线程 B 并不是直接去主存中读取共享变量的值,而是先在本地内存 B 中找到这个共享变量,发现这个共享变量已经被更新了,然后本地内存 B 去主存中读取这个共享变量的新值,并拷贝到本地内存 B 中,最后线程 B 再读取本地内存 B 中的新值。
(3)JMM如何保证内存可见性
Java内存模型(JMM)的核心作用之一就是解决"如何知道共享变量被其他线程更新了"这个问题。
JMM 通过控制主存与每个线程的本地内存之间的交互,来提供内存可见性保证。
Java 中的 volatile 关键字可以保证多线程操作共享变量的可见性以及禁止指令重排序,synchronized 关键字不仅保证可见性,同时也保证了原子性(互斥性)。
在更底层,JMM 通过内存屏障来实现内存的可见性以及禁止重排序。为了程序员更方便地理解,设计者提出了 happens-before 的概念,它更加简单易懂,从而避免了程序员为了理解内存可见性而去学习复杂的重排序规则,以及这些规则的具体实现方法。
2.2,JMM与重排序
Java内存模型(JMM)的一个重要方面就是管理指令重排序(Reordering),它定义了在多线程环境下哪些重排序是被允许的,哪些是被禁止的。理解这一点对编写正确的并发程序至关重要。
(1)指令重排序的类型
- 编译器优化的重排序
编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
- 指令级并行的重排序
现代处理器采用指令级并行技术(ILP)来将多条指令重叠执行
- 内存系统的重排序
由于处理器使用缓存和读写缓冲区,使得加载和存储操作看上去可能是在乱序执行
(2)JMM如何限制重排序
JMM通过以下几种机制来限制重排序:
- happens-before规则
定义了一系列天然的happens-before关系,在这些关系下禁止重排序:
- 程序顺序规则
- 监视器锁规则
- volatile变量规则
- 线程启动/终止规则
- 传递性规则
- 内存屏障(Memory Barrier)
JMM在关键位置插入内存屏障指令来禁止特定类型的重排序:
屏障类型 | 作用 |
---|---|
LoadLoad | 禁止Load1与Load2重排序 |
StoreStore | 禁止Store1与Store2重排序 |
LoadStore | 禁止Load与后续Store重排序 |
StoreLoad | 全能屏障,禁止Store与后续Load重排序(开销最大) |
- 特殊关键字语义
- volatile:禁止与相邻指令重排序
- final:保证正确构造后的对象对所有线程可见
- synchronized:进入/退出时隐含内存屏障
2.3顺序一致性模型
(1)核心定义
顺序一致性模型必须满足两个基本条件:
- 程序顺序保留:每个线程内部的操作必须按照该线程的程序代码顺序执行。(不允许重排序)
- 全局内存顺序:所有线程看到的整个系统的操作执行顺序必须一致
顺序一致性模型虽然理论上完美,但硬件上难以实现,但Java等语言可以提供近似保证。
(2)同步程序的顺序一致性效果
在并发编程中,通过同步机制可以使程序表现出顺序一致性的内存效果,即使底层硬件和编译器可能进行各种优化。
相关文章:

JMM初学
文章目录 1,线程间的同步和通信1.1, 共享内存并发模型 (Shared Memory Model)线程通信机制线程同步机制特点 1.2, 消息传递并发模型 (Message Passing Model)线程通信机制线程同步机制特点 适用场景对比 2,Java内存模型JMM2.0,Java内存模型的基础(1)内存…...
transformer和 RNN以及他的几个变体区别 改进
Transformer、RNN 及其变体(LSTM/GRU)是深度学习中处理序列数据的核心模型,但它们的架构设计和应用场景有显著差异。以下从技术原理、优缺点和适用场景三个维度进行对比分析: 核心架构对比 模型核心机制并行计算能力长序列依赖处…...

构建云原生安全治理体系:挑战、策略与实践路径
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:从传统安全走向“云原生安全” 随着企业 IT 架构从传统单体系统向容器化、微服务和云原生平台转型…...
vue-print-nb 打印相关问题
一、背景与解决方案 1、ElementUI表格打印通病,均面临边框丢失、宽度超出问题:相关解决代码有注释; 2、大多数情况下不会打印页眉页脚的日期、网址、未配置popTitle显示的undefined:相关解决代码有注释; 3、打印预览页…...

vcs仿真产生fsdb波形的两种方式
目录 方法一: 使用verilog自带的系统函数 方法二: 使用UCLI command 2.1 需要了解什么是vcs的ucli,怎么使用ucli? 2.2 使用ucli dump波形的方法 使用vcs仿真产生fsdb波形有两种方式,本文参考《vcs user guide 20…...
每日算法 -【Swift 算法】三数之和
Swift|三数之和(3Sum)详细题解 注释 拓展(LeetCode 15) ✨题目描述 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a, b, c,使得 a b c 0。请你找出所有和为 0 且不重…...

Go语言底层(三): sync 锁 与 对象池
1. 背景 在并发编程中,正确地管理共享资源是构建高性能程序的关键。Go 语言标准库中的 sync 包提供了一组基础而强大的并发原语,用于实现安全的协程间同步与资源控制。本文将简要介绍 sync 包中常用的类型和方法: sync 锁 与 对象池,帮助开发…...
登高架设作业操作证考试:理论题库高频考点有哪些?
一、安全基础知识 法律法规 《安全生产法》《特种作业人员安全技术培训考核管理规定》中关于登高作业的强制性要求(如持证上岗、培训时限等)。 事故责任划分:未系安全带、无监护作业等违规行为的法律后果。 个人防护 安全带使用标准&#…...

2025年06月06日Github流行趋势
项目名称:agent-zero 项目地址url:https://github.com/frdel/agent-zero项目语言:Python历史star数:8958今日star数:324项目维护者:frdel, 3clyp50, linuztx, evrardt, Jbollenbacher项目简介:A…...
华为云CentOS配置在线yum源,连接公网后,逐步复制粘贴,看好自己对应的版本即可,【新手必看】
华为云镜像源配置 YUM 源的详细步骤: 1. 备份原有的 YUM 源配置文件 在修改 YUM 源之前,建议备份原有的配置文件。通常,YUM 源的配置文件位于 /etc/yum.repos.d/ 目录下。例如,备份 CentOS 的默认 YUM 源配置文件: …...
http头部注入攻击
1.HTTP请求的组成部分 HTTP(HyperText Transfer Protocol)请求由 请求行(Request Line)、请求头(Headers)、空行(Blank Line)和请求体(Request Body) 组成。具体结构如下: 1. 请求行(Request Line) 请求行是HTTP请求的第一行,包含三个部分…...
三类 Telegram 账号的风控差异分析与使用建议
在使用 Telegram 过程中,很多用户会遇到账号被限制、封禁、加群失败等问题。除了操作行为外,账号本身的注册方式、活跃时间、环境匹配程度也会直接影响风控等级。 本篇文章从账号风控角度出发,分析三类常见 Telegram 账号的特点与适用环境&am…...
Matlab | matlab中的点云处理详解
点云处理 ⚙️ **一、点云基础操作**🧹 **二、点云预处理**📊 **三、特征提取与分析**🔄 **四、点云配准(对齐点云)**🔷 **五、三维重建与应用**⚡️ **六、高级功能与性能优化**💎 **七、实战技巧与参数调优**📚 **学习资源**MATLAB 的点云处理能力主要依赖 Poi…...
【机试题解法笔记】寻找最大价值的矿堆
题目 给你一个由 0(空地)、1(银矿)、2(金矿) 组成的的地图,矿堆只能由上下左右相邻的金矿或银矿连接形成。超出地图范围可以认为是空地。 假设银矿价值 1,金矿价值 2,请你找出地图中最大价值的矿堆并输出该矿堆的价值。 输入描述 地图元素信…...

动态规划 熟悉30题 ---上
本来是要写那个二维动态规划嘛,但是我今天在问题时候,一个大佬就把他初一时候教练让他练dp的30题发出来了(初一,啊虽然知道计算机这一专业,很多人从小就学了,但是我每次看到一些大佬从小学还是会很羡慕吧或…...
嵌入式学习笔记- freeRTOS 带FromISR后缀的函数
FreeRTOS中带FromISR后缀的函数 是用于中断的函数,它有两个特点 一个是无等待延时, 一个是无立刻触发任务切换, 那么 一 为什么中断中不能等待(阻塞)? 因为中断中等待的,一般都是任务给予的…...

Linux系统:ELF文件的定义与加载以及动静态链接
本节重点 ELF文件的概念与结构可执行文件,目标文件ELF格式的区别ELF文件的形成过程ELF文件的加载动态链接与静态链接动态库的编址与方法调用 一、ELF文件的概念与结构 1.1 文件概述 ELF(Executable and Linkable Format)即“可执行与可链…...
迷宫与陷阱--bfs+回路+剪枝
1.用bfs板子,同时会出现回路,但不能不用bo数组,要减去一部分没有用的回路 2.什么叫没有用的回路--因为我有无敌了,以前遇到的陷阱就能过了,那这就是有用的回路, 所以我记录(x,y)点…...

【国产化适配】如何选择高效合规的安全数据交换系统?
一、安全数据交换系统的核心价值与国产化需求 在数字化转型浪潮中,企业数据流动的频率与规模呈指数级增长,跨网文件传输已成为日常运营的刚需,所以安全数据交换系统也是企业必备的工具。然而,数据泄露事件频发、行业合规要求趋严…...
基于深度学习的裂缝检测与分割研究方向的 数据集介绍
目录 一、基于深度学习的裂缝检测与分割研究方向 1. 任务定义与挑战 2. 主流方法与技术演进 3. 实际应用优化 二、裂缝检测与分割常用数据集详解 1. SDNET2018 2. CrackTree(CrackTree200) 3. AigleRN 4. CFD(Concrete Crack Detect…...
【Prompt实战】国际翻译小组
本文原创作者:姚瑞南 AI-agent 大模型运营专家/音乐人/野生穿搭model,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。&#…...

简化复杂系统的优雅之道:深入解析 Java 外观模式
一、外观模式的本质与核心价值 在软件开发的世界里,我们经常会遇到这样的场景:一个复杂的子系统由多个相互协作的类组成,这些类之间可能存在错综复杂的依赖关系和交互逻辑。当外部客户端需要使用这个子系统时,往往需要了解多个类…...

设计模式杂谈-模板设计模式
在进入正题之前,先引入这样一个场景: 程序员A现在接到这样一个需求:这个需求有10个接口,这些接口都需要接收前端的传参,以及给前端返回业务状态信息。出于数据保密的要求,不管是前端传参还是最终参数返回都…...
LangChain【8】之工具包深度解析:从基础使用到高级实践
文章目录 1. LangChain工具包概述1.1 工具包的基本概念1.2 工具包的主要类型 2. SQL数据库工具包深度解析2.1 基本配置与初始化2.2 数据库连接与验证2.3 工具包初始化与工具获取2.4 创建Agent并执行查询2.5 完整代码 3. 高级使用技巧3.1 自定义工具集成3.2 多工具包组合使用3.3…...

C#入门学习笔记 #6(字段、属性、索引器、常量)
欢迎进入这篇文章,文章内容为学习C#过程中做的笔记,可能有些内容的逻辑衔接不是很连贯,但还是决定分享出来,由衷的希望可以帮助到你。 笔记内容会持续更新~~ 将这四种成语放在一起讲是因为这四种成员都是用来表达数据的。 字段…...

广目软件GM DC Monitor
广目(北京)软件有限公司成立于2024年,技术和研发团队均来自于一家具有近10年监控系统研发的企业。广目的技术团队一共实施了9家政府单位、1家股份制银行、1家芯片制造企业的数据中心监控预警项目。这11家政企单位由2家正部级、1家副部级、6家…...

每日八股文6.6
每日八股-6.6 Mysql1.怎么查看一条sql语句是否走了索引?2.能说说 MySQL 事务都有哪些关键特性吗?3.MySQL 是如何保证事务的原子性的?4.MySQL 是如何保证事务的隔离性的?5.能简单介绍一下 MVCC 吗?或者说,你…...
动静态库的使用(Linux下)
1.库 通俗来说,库就是现有的,可复用的代码,例如:在C/C语言编译时,就需要依赖相关的C/C标准库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。通常我们可以在windows下看到一些后…...

PostgreSQL17 编译安装+相关问题解决
更新时间:2025.6.6,当前最新稳定版本17.5,演示的是17.5,最新测试版本18beta1 演示系统:debian12 很多时候,只有编译安装才能用上最新的软件版本或指定的版本。这也是编译安装的意义。 一、编译安装 &…...
FFMPEG 提取视频中指定起始时间及结束时间的视频,给出ffmpeg 命令
以下是提取视频中指定起始时间及结束时间的 ffmpeg 命令示例: bash 复制 ffmpeg -i input.mp4 -ss 00:01:30.00 -to 00:05:00.00 -c copy output.mp4 其中,-i input.mp4 是指定要处理的输入视频文件为 “input.mp4”。 -ss 00:01:30.00 表示指定视频的起始时间为 1 分 30 …...