首页/新闻资讯/正文详情

【Java并发】用 JMM 与 Happens-Before 解决多线程可见性与有序性问题

发布时间:2026/11/3 2:01:19 来源:云帆数科 栏目:资讯中心
【Java并发】用 JMM 与 Happens-Before 解决多线程可见性与有序性问题
文章目录1. 问题与场景多线程下为何出现「看不见、顺序乱」2. JMM 基础主内存与本地内存的抽象3. Happens-Before约定「何时可见」4. volatile可见性与禁止重排5. 管程与 synchronized互斥与块内可见性6. final 域不可变对象的安全发布7. 实际开发与架构设计中的注意点总结与参考链接以库存扣减为例——多线程同时扣减同一库存时会出现「扣完了别的线程还读到旧值」导致超卖或「先判断库存再扣减」被重排成「先扣减再判断」导致逻辑错乱。根因是 CPU 缓存和指令重排写操作未必立刻对其它线程可见代码顺序也未必等于执行顺序。JMM 用主内存 / 本地内存抽象和Happens-Before规则约定「何时对另一线程可见」再通过volatile如扣减完成标志、synchronized扣减块互斥、final库存上限等配置的安全发布把规则落地。核心就一句先有「何时可见」的约定Happens-Before再用关键字和锁去实现业务才能安全地写多线程共享变量。下文按「现象与根因 → JMM 与 Happens-Before → volatile/synchronized/final 用法与边界 → 实际开发注意点」展开文末总结与参考链接。1. 问题与场景多线程下为何出现「看不见、顺序乱」多线程共享变量时常出现两类现象写完了别的线程看不见可见性、代码顺序和执行顺序不一致有序性于是出现脏读、重复扣减、状态不一致等难以复现的 bug。根因简要可见性CPU 有缓存写往往先到缓存再异步刷主存其他线程读到的可能是旧缓存。有序性编译器/CPU 会重排指令在不改变单线程语义的前提下打乱顺序多线程下可能把「先读后写」重排成「先写后读」从而读到未初始化的值。若没有统一规范既无法判断「何时对另一线程可见」也无法在优化与正确性之间做权衡。因此需要一套约定「何时可见」的规范并让具体的关键字和锁去实现它——这正是下文第 2、3 节要讲的 JMM 与 Happens-Before以及第 4、5、6 节的 volatile/synchronized/final2. JMM 基础主内存与本地内存的抽象要约定「何时对另一线程可见」先要对「谁在哪儿、何时同步」有一层统一描述。JMM 用「主内存线程本地内存」的抽象来做到这一点共享变量在主内存每个线程有一份「本地内存」涵盖缓存、写缓冲区、寄存器以及编译器重排等读/写先与本地内存交互再与主内存同步。JMM 通过控制「主内存与各线程本地内存之间的交互时机」来提供内存可见性保证。在这种抽象下线程间通信被规范为两步① 线程 A 将本地内存中更新过的共享变量刷新到主内存② 线程 B从主内存读取A 已更新过的共享变量。也就是说可见性由「何时刷新、何时读」的规则决定——这套规则就是下一节的 Happens-Before。3. Happens-Before约定「何时可见」上一节说到可见性由「何时刷新、何时读」的规则决定在 JMM 里这套规则就是Happens-Before若A Happens-Before B则 A 的结果对 B 可见。它约束的是可见性顺序而不是执行顺序——允许不相关操作重排但不允许破坏 Happens-Before 关系这样既满足业务对可见性的需求又给编译器和 CPU 留出优化空间。常用规则与使用场景规则含义典型场景程序顺序同一线程内前面的修改对后面可见单线程内逻辑顺序可推理volatile 变量对 volatile 的写对后续该变量的读可见标志位、开关如「x 已写好」用 vtrue 表示传递性A→BB→C ⇒ A→C组合多条规则推导跨线程可见性管程中锁解锁 Happens-Before 后续对该锁的加锁synchronized块内修改对下一进入该锁的线程可见线程 start()主线程 start 前操作对子线程可见传给子线程的初始参数、配置线程 join()子线程结束对 join 返回后的主线程可见子线程计算结果对主线程可见线程中断interrupt() 先于被中断线程检测到中断中断请求一定早于中断处理示例volatile 做可见性桥梁classVolatileExample{intx0;volatilebooleanvfalse;publicvoidwriter(){x42;// 程序顺序先于 vtruevtrue;// volatile 写对后续读 v 可见}publicvoidreader(){if(vtrue){// 传递性 ⇒ x42 对当前线程可见// x 在 1.5 上等于 42}}}上面用v做「x 已写好」的信号volatile 写与读建立 Happens-Before再通过传递性把x42的可见性带给 reader无需锁整块代码。volatile 正是实现「对 volatile 变量的写对后续读可见」这条 Happens-Before 规则的关键字下一节单独说它的语义与边界。4. volatile可见性与禁止重排Happens-Before 里有一条是「对 volatile 的写对后续该变量的读可见」volatile关键字就是实现这条规则的手段。语义对该变量的读写与主内存直接交互不经过线程本地缓存可见性。禁止对该变量相关操作的指令重排有序性。实现上通过读/写前后插入内存屏障保证。适用场景单变量状态、标志位写一次、多线程尽快可见如关闭开关、初始化完成标志。与 Happens-Before 配合做「可见性桥梁」如上节示例。边界与注意不保证原子性i 等复合操作仍需synchronized或原子类。仅限本变量只保证该变量本身及其前后顺序不保证其他共享变量的可见性需通过传递性组合规则。当需求不只是「单变量可见」而是「同一时刻只有一个线程执行某段代码」或「先检查再更新」这类复合操作时就要用到synchronized见下一节。5. 管程与 synchronized互斥与块内可见性volatile 只保证单变量可见性不保证互斥和原子性。需要互斥或「先读后改」的原子性时要用synchronized它对应 Happens-Before 里的「管程中锁」规则——解锁 Happens-Before 后续对该锁的加锁。语义管程Monitor通用同步原语synchronized是 Java 对管程的实现。JMM 规则解锁 Happens-Before 后续对该锁的加锁→ 块内对共享变量的写在释放锁后对下一个获得同一把锁的线程可见。适用场景需要互斥且块内修改对下一进入者可见如判断并更新库存、先检查再赋值。需要原子性的复合操作如 i、check-then-act。示例synchronized(this){if(this.x12){this.x12;}}// 解锁后下一个获得 this 锁的线程能看到 x12还有一种常见需求是「构造完成后不再变的配置或引用希望无锁地安全发布给多线程」——这由final配合 JMM 对 final 域的禁止重排规则来保证见下一节。6. final 域不可变对象的安全发布JMM 对final域做了特殊规定禁止把对 final 域的写重排到构造函数 return 之外从而在无锁的前提下实现「构造完成即对其它线程可见」的安全发布。语义基本类型 final禁止把对 final 域的写重排到构造函数 return 之外final 写之后、return 前插入 StoreStore 屏障→ 对象发布时 final 已写好。引用类型 final构造函数内对 final 引用所指对象的写不能与「把该引用赋给引用变量」重排 → 避免发布半初始化对象。适用场景构造完成后不再变的配置、常量、引用希望构造完成后对所有线程立即可见且无需加锁。不可变对象final 基本类型、String、枚举、Long/Double/BigInteger/BigDecimal 等一旦正确发布可被多线程安全使用AtomicInteger/AtomicLong 为可变需按原子类语义使用。7. 实际开发与架构设计中的注意点前面几节分别讲了 JMM 与 Happens-Before 的约定以及 volatile、synchronized、final 的语义与场景。实际开发中需要把这些串起来什么时候选谁、各有什么边界、出问题时怎么排查。下面按选型、边界与排查三块简要梳理。选型标准诉求选型说明单变量状态/标志位仅需可见性volatile轻量不保证复合操作原子性需要互斥或复合操作原子性、块内修改对下一进入者可见synchronized锁粒度尽量小避免全局锁构造后不变、安全发布final 正确构造无锁发布依赖 JMM 禁止重排边界与局限volatile不适用于 i、check-then-act 等复合操作多变量可见性需依赖 Happens-Before 传递性设计。synchronized需明确锁对象同一对象才互斥注意锁顺序避免死锁。final仅保证构造完成时的可见性构造后若通过其他引用修改对象内部状态仍需其他同步手段。问题排查思路现象多线程下偶尔读到旧值或未初始化值。定位先确认是否为可见性/有序性是否未用 volatile/synchronized/final或跨线程可见性未满足 Happens-Before再结合 Thread Dump 看是否阻塞、死锁。措施按诉求补 volatile单变量可见、synchronized互斥可见、或 final安全发布复核共享变量的读写是否都在同一锁或同一 Happens-Before 链上。总结与参考链接回到开头的库存扣减场景要避免「扣完了别人还读到旧值」导致的超卖以及「先判断再扣减」被重排导致的逻辑错乱就需要先理解「何时对另一线程可见」JMM 与 Happens-Before再把约定落到代码里volatile/synchronized/final。全文要点如下。总结现象与根因多线程下「看不见、顺序乱」来自 CPU 缓存与指令重排JMM 用主内存/本地内存抽象和 Happens-Before 约定「何时可见」。手段与边界volatile 做单变量可见与禁止重排不保证原子性synchronized 做互斥与块内可见final 做无锁安全发布。按诉求选型注意各手段的边界与组合时的 Happens-Before 关系。排查先确认是否为可见性/有序性未用或误用 volatile/synchronized/final再结合 Thread Dump 看阻塞与死锁按诉求补手段并复核 Happens-Before 链。参考链接主题链接volatile 深入java 并发关键字volatile 深入浅出synchronized 深入关键字synchronized 详解JMM 总览pdai - JMM

相关推荐

Qwen3-Omni-30B-A3B-Instruct智能洗车机:车辆音视频检测与清洗系统
Qwen3-Omni-30B-A3B-Instruct智能洗车机:车辆音视频检测与清洗系统

Qwen3-Omni-30B-A3B-Instruct智能洗车机:车辆音视频检测与清洗系统 Qwen3-Omni-30B-A3B-Instruct是一款多语言全模态模型,原生支持文本、图像、音视频输入,并能实时生成语音。本文将介绍如何利用这一强大模型构建智能洗车机的车辆音视频检测与… · 2026/11/3 2:01:19

WanVideo fp8模型:ComfyUI视频生成效率新引擎
WanVideo fp8模型:ComfyUI视频生成效率新引擎

WanVideo fp8模型:ComfyUI视频生成效率新引擎 导语:WanVideo_comfy_fp8_scaled模型正式发布,通过FP8量化技术为ComfyUI视频生成工作流带来显著效率提升,在保持接近FP16质量的同时降低计算资源需求。 行业现状:随着AIGC… · 2026/11/3 2:00:19

ByteFF2:AI力场如何实现液体特性量子突破?
ByteFF2:AI力场如何实现液体特性量子突破?

ByteFF2:AI力场如何实现液体特性量子突破? 导语:字节跳动最新发布的ByteFF2模型通过图神经网络(GNN)构建可极化力场,首次实现无需实验校准即可高精度预测有机液体和电解质的物理化学性质,为材料… · 2026/11/3 2:00:19

24.House of Force 从零到 getshell
24.House of Force 从零到 getshell

📎my_force.zip House of Force 从零到 getshell —— 一道自命题的完整调试记录 这是一道关于 House of Force 的自命题,名字叫 my_force,题目本身不复杂,主要用到的知识点是堆溢出。写这篇文章的目的不只是记一下 exp&#xf… · 2026/11/3 2:54:28

不买订阅、不交账号:这款开源工具把53平台分发做成了本地软件(github封神项目:自由发)
不买订阅、不交账号:这款开源工具把53平台分发做成了本地软件(github封神项目:自由发)

一个“反SaaS”的自媒体分发工具:为什么有人把53个平台的发布权,塞回了你自己的电脑里 #开源工具 #本地运行 #自媒体分发 #数据主权 #浏览器自动化 #Electron应用 如果你同时运营三个以上的内容平台,大概率经历过同一种疲惫:一篇文… · 2026/11/3 2:54:28

【C++高并发内存池】 2.ThreadCache设计
【C++高并发内存池】 2.ThreadCache设计

目录 前言: 1.Common.hpp 2.UnitTest.cpp 3. ConCurrentAlloc.h 4. ThreadCache.h 5.ThreadCache.cpp 设计总结: 前言: 前面一个设计了一个定长内存池,经过与传统的内存申请方式对比,初步实现了一个可承受频繁… · 2026/11/3 2:54:28

一次生成还是多轮修补:两种工作流的真实产出效率对比
一次生成还是多轮修补:两种工作流的真实产出效率对比

一次生成还是多轮修补:两种工作流的真实产出效率对比 用 4B 小模型生成一个能跑的单文件 Three.js 场景,真正的分岔点不在提示词写得多漂亮,而在第一次生成之后你做什么选择:重新掷骰子,还是拿着上一版代码打补丁。前… · 2026/11/3 2:54:28

新手也能上手!2026年靠谱AI论文工具榜单,AI工具一键写高质论文
新手也能上手!2026年靠谱AI论文工具榜单,AI工具一键写高质论文

2026 年实测 10 款主流 AI 论文工具,千笔AI以全流程覆盖 语义级降重 免费查重领跑综合榜;ThouPen 稳坐留学生毕业全流程工具头把交椅;免费工具中DeepSeek Scholar、豆包学术版表现亮眼,30 分钟即可生成万字高质量初稿&#xff0… · 2026/11/3 2:54:28

都说“数据是资产“,到底什么意思?我用三个问题判断你那堆数据算不算
都说“数据是资产“,到底什么意思?我用三个问题判断你那堆数据算不算

上周跟一个老板吃饭,他说了一句特别有代表性的话:“我公司最值钱的就是这些客户资料,记了三年了。” 我随口问了句:这三年里,哪个客户最赚钱?哪个客户在慢慢不下单了? 他答不上来。 不是他不聪明… · 2026/11/3 2:53:28

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码