Skip to content

Java 内存模型与并发编程

一、并发编程模型分类

  • 关键问题:线程通信与同步。
  • 通信机制
  • 共享内存:线程通过读写共享状态隐式通信(如 Java)。
  • 消息传递:线程通过显式消息通信,无共享状态。
  • 同步机制
  • 共享内存:显式同步,程序员需指定互斥代码。
  • 消息传递:隐式同步,发送先于接收。

Java 特点:采用共享内存模型,通信隐式,易引发可见性问题。


二、Java 内存模型(JMM)抽象

  • 存储位置
  • 共享变量(实例域、静态域、数组元素):存储在堆中,线程共享。
  • 私有变量(局部变量、方法参数、异常参数):存储在栈中,线程隔离,无可见性问题。
  • 抽象模型
  • 主内存:存储共享变量。
  • 本地内存:线程私有,保存共享变量副本(抽象概念,涵盖缓存、寄存器等)。
  • 通信步骤
  • 线程更新本地内存中的共享变量副本。
  • 将更新刷新到主内存。
  • 其他线程从主内存读取更新。
  • 目的:通过控制主内存与本地内存交互,保证内存可见性。

三、重排序

  • 类型
  • 编译器重排序:优化语句顺序,不改变单线程语义。
  • 指令级并行重排序:处理器重叠执行指令,无数据依赖可调整顺序。
  • 内存系统重排序:缓存和写缓冲区导致操作乱序。
  • 过程:源代码 → 编译器重排序 → 指令重排序 → 内存重排序 → 执行序列。
  • 问题:多线程下可能破坏可见性。
  • JMM 对策
  • 禁止特定编译器重排序。
  • 通过内存屏障禁止处理器重排序。

四、处理器重排序与内存屏障

  • 写缓冲区影响
  • 写操作暂存缓冲区,仅当前处理器可见。
  • 可能导致写-读操作重排序。
  • 处理器重排序类型(常见处理器):
  • Store-Load:普遍允许。
  • 数据依赖:禁止重排序。
  • 内存屏障
  • LoadLoad:确保加载顺序。
  • StoreStore:确保存储顺序。
  • LoadStore:加载先于存储。
  • StoreLoad:存储刷新后加载,全能型,开销大。
  • 作用:JMM 插入屏障,限制重排序,保证可见性。

五、Happens-Before

  • 定义(JSR-133):
  • 一个操作结果对后续操作可见,且顺序在前。
  • 不要求严格时间顺序。
  • 规则
  • 程序顺序:线程内操作顺序。
  • 监视器锁:解锁 → 加锁。
  • volatile:写 → 读。
  • 传递性:A → B,B → C,则 A → C。
  • 与 JMM:对应重排序规则,简化程序员理解。

六、数据依赖性与 As-If-Serial

  • 数据依赖
  • 写后读写后写读后写:重排序改变结果,禁止。
  • 仅限单线程或单处理器。
  • As-If-Serial
  • 单线程执行结果不变。
  • 无数据依赖可重排序。
  • 示例pi * r * rpir 可重排,但计算依赖不可。

七、重排序对多线程影响

  • 示例java class ReorderExample { int a = 0; boolean flag = false; public void writer() { a = 1; flag = true; } public void reader() { if (flag) int i = a * a; } }
  • 无同步
  • a = 1flag = true 可重排序。
  • 线程 B 可能读到 flag = truea = 0
  • 控制依赖
  • if (flag)a * a 重排序,猜测执行破坏语义。

八、顺序一致性

  • 定义
  • 线程按程序顺序执行。
  • 操作原子且立即对所有线程可见。
  • 数据竞争
  • 无同步的读写操作。
  • 导致不可预测结果。
  • JMM 保证
  • 正确同步程序具有顺序一致性。

九、同步与未同步程序

  • 同步程序
  • 使用 synchronizedjava class SynchronizedExample { int a = 0; boolean flag = false; public synchronized void writer() { a = 1; flag = true; } public synchronized void reader() { if (flag) int i = a; } }
  • 顺序一致性:临界区内重排序不影响结果。
  • 未同步程序
  • 最小安全性:读取值为默认值或之前写入值。
  • 不保证顺序一致性。
  • 64 位变量读写可能非原子。

十、处理器内存模型

  • 类型
  • TSO:允许写-读重排序。
  • PSO:加允许写-写。
  • RMO/PowerPC:更宽松。
  • 与 JMM
  • JMM 通过屏障适配不同处理器。
  • 比顺序一致性弱,优化性能。

十一、JMM 设计

  • 平衡
  • 程序员:强可见性,简单易用。
  • 编译器/处理器:弱约束,优化空间。
  • 策略
  • 禁止改变结果的重排序。
  • 允许不影响结果的重排序。
  • 内存可见性
  • 单线程:As-If-Serial。
  • 正确同步:顺序一致性。
  • 未同步:最小安全性。

十二、JSR-133 改进

  • Volatile:禁止与普通变量重排序,写-读如锁。
  • Final:初始化安全性,禁止重排序。

总结

  • JMM 核心:通过 Happens-Before 和屏障,平衡可见性与性能。
  • 并发基础:理解通信、同步与重排序是编写正确多线程程序的关键。