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

Java 内存模型(JMM):happens-before、可见性与有序性怎么落到实战

目标:你能解释清楚为什么会出现“明明赋值了但另一个线程看不到”、为什么会出现“指令重排导致半初始化对象”、以及volatile/synchronized/final分别在解决什么问题。

1. 为什么需要 JMM:CPU/编译器都在“帮你优化”

你写的 Java 代码,最终会经历:

  • 编译器优化(JIT)
  • CPU 乱序执行
  • 多级缓存(L1/L2/L3)

这意味着:

  • 同一段代码在不同线程看到的执行顺序可能不一致(有序性问题)
  • 一个线程写入的值,另一个线程未必立刻可见(可见性问题)

JMM 的核心作用:在“允许优化”的前提下,给并发程序一个可推理的规则体系

2. 三大性质:原子性、可见性、有序性

  • 原子性:一个操作不可被中断。比如对int的单次读写是原子的,但i++不是。
  • 可见性:一个线程写入,另一个线程能及时看到。
  • 有序性:程序执行顺序符合你推理的顺序(至少符合 happens-before)。

关键点:JMM 并不保证“全局绝对顺序”,而是通过 happens-before 提供“必要的顺序”。

3. happens-before:不是“先执行”,是“先对你可见”

happens-before(HB)可以理解为:

  • 如果 A HB B,那么 A 的结果对 B 可见,并且 A 的执行顺序排在 B 之前(对推理而言)。

3.1 你必须背的 HB 规则(但要理解它在干嘛)

  • 程序顺序规则:单线程内,按代码顺序 HB
  • 监视器锁规则unlockHB 后续对同一锁的lock
  • volatile 规则:对同一变量的volatile writeHB 后续volatile read
  • 传递性:A HB B 且 B HB C,则 A HB C
  • 线程启动规则Thread.start()HB 线程内的动作
  • 线程终止规则:线程内动作 HBThread.join()返回

这些规则的本质:在关键位置插入“屏障语义”,让你能建立推理链。

4.volatile:只解决“可见性 + 有序性”,不保证复合操作原子性

4.1 它解决什么

  • 可见性:写入后其它线程读到最新值
  • 有序性:禁止关键的重排(写前的操作不会跑到写后,读后的操作不会跑到读前)

4.2 它不解决什么

  • count++的原子性(读-改-写是 3 步)

4.3 典型用法:状态标记 / 双重检查

  • 停止标记volatile boolean stopped
  • 配合 DCL(双重检查锁)实现延迟初始化

DCL 里必须用 volatile 的直觉:避免“引用先发布、对象还没初始化完”的重排。

5.synchronized:除了互斥,还提供 HB 保证

很多人只把 synchronized 当“互斥锁”,但它还有两件事:

  • 进入同步块:相当于获取屏障,保证能看到之前释放锁线程的写入
  • 退出同步块:相当于释放屏障,把本线程写入刷新出去

所以synchronized同时解决:

  • 原子性(互斥)
  • 可见性(HB)
  • 有序性(临界区内的顺序可推理)

6.final:不是“不变”这么简单,它还有安全发布语义

final字段在构造完成后,会有特殊的“冻结”语义:

  • 只要对象被正确发布,其他线程看到该对象时,也能看到 final 字段的正确值。

这就是为什么不可变对象(如 String)在并发下天然安全。

7. 安全发布(实战比背 JMM 更重要)

你真正需要保证的是:对象被别的线程拿到时,状态已经一致。

常见的安全发布方式:

  • 静态初始化(类初始化阶段天然线程安全)
  • 通过volatile引用发布
  • 通过锁发布(在 synchronized 保护下写入,然后读也用同一把锁)
  • 通过并发容器发布(如ConcurrentHashMap

反例:

  • 把对象引用写入普通共享变量,没有任何同步手段

8. 常见坑与排查

8.1 “标记位不生效”

  • 现象:一个线程把stop=true,另一个线程循环里一直看不到
  • 根因:缺少可见性保证
  • 修复:volatile或锁,或改成AtomicBoolean

8.2 DCL 没加 volatile 导致半初始化

  • 现象:极低概率 NPE / 字段为默认值
  • 根因:指令重排
  • 修复:DCL 的 instance 必须是volatile

8.3 用AtomicInteger但仍然线程不安全

  • 现象:计数没问题,但对象其它字段错乱
  • 根因:你只保证了一个变量的原子性,没有保证“多字段一致性”
  • 修复:把一致性放到锁里,或者把状态收敛到不可变对象整体替换

9. 面试背诵稿(45 秒)

JMM 要解决的是多线程下的可见性和有序性问题,因为编译器和 CPU 会做重排且有多级缓存。
我们用 happens-before 来推理:比如 unlock happens-before 后续 lock,volatile 写 happens-before 后续读,并且具备传递性。
volatile提供可见性和一定的有序性,但不保证复合操作原子性;synchronized既互斥保证原子性,也通过进入/退出监视器建立 happens-before。
实战里最关键的是“安全发布”:对象发布要通过 volatile/锁/并发容器或类初始化,否则可能出现半初始化对象和不可见更新。

http://www.jsqmd.com/news/570421/

相关文章:

  • HunyuanVideo-Foley开箱即用:基于RTX4090D的私有化部署与快速上手体验
  • OpCore-Simplify:重新定义Hackintosh配置的艺术
  • 别再傻傻分不清!NTC和PTC热敏电阻,从家电维修到电路设计,教你一眼选对
  • Stata大数据处理终极指南:如何用ftools让数据分析速度提升10倍
  • Windows下VMware安装macOS避坑指南:从系统配置到Xcode完美运行
  • Rudist 0.4.3:让 Agent 接管你的Redis
  • 三指拖动功能:跨系统用户的触控手势优化与效率提升方案
  • clHttp报告“上下文已过期,不能再用了”
  • OpenSubdiv高级特性:特征自适应细分与硬件曲面细分
  • 顶礼膜拜一下
  • 深入解析RF测试中的S参数:从阻抗匹配到信号传输的全面指南
  • LogonTracer核心功能深度解析:4624、4625等关键事件ID的实战应用
  • 旧设备复活超实用指南:开源工具助力老Mac系统升级
  • 深圳腕表维修避坑大全:从百达翡丽到浪琴,六城12,000次案例揭示的真相与教训 - 时光修表匠
  • GDScript快速上手:3天从零基础到游戏开发的完整指南
  • Live Avatar镜像免配置实测:手把手教你搭建无限长度数字人视频
  • JavaWEB的三大组件之一---监听器Listener
  • 成都装修行业进入“存量厮杀”阶段:十家机构横向对比,谁在真正兑现“所见即所得”? - 推荐官
  • 宝塔面板访问地址丢失?快速找回的实用指南
  • 零代码部署社区门禁:AI读脸术镜像快速安装教程
  • Livebook共享密钥管理终极指南:团队协作中的安全数据共享解决方案
  • SuGaR与NeRF对比分析:为什么高斯泼溅是未来趋势
  • 2026年全国医师进修办理服务机构深度测评 - 深度智识库
  • 快速体验AI识别:ResNet18镜像部署与使用完整教程
  • CHORD-X在MATLAB数据分析工作流中的应用:自动生成仿真实验报告
  • Keil工程窗口那些带叹号、星号、钥匙的图标,到底在提醒你什么?
  • 5G NR PUCCH格式0与格式2实战解析:如何优化ACK/NACK反馈性能
  • OpenJSCAD.org扩展开发完全手册:从零开始创建自定义IO格式
  • 5分钟学会OrgChart:从零开始创建动态组织图
  • GEO 优化是什么?2026 年 4 月实测全国 5 家头部 GEO 优化服务商,这份选型指南请收好 - 博客湾