Java 内存模型

打印 上一主题 下一主题

主题 1944|帖子 1944|积分 5832

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Java 内存模型

Java 内存模型(Java Memory Model,简称 JMM),是 Java 并发编程的焦点底层规范,重要用于描述 Java 中内存对象的可见性处理逻辑。它界说了线程和主内存之间的交互规则,解决多线程环境下数据不同等、指令实行顺序杂乱等问题,是理解 volatile、synchronized 等关键字底层原理的基石。

多线程交互的本质:共享内存的「数据对话」

多线程之间存在两种交互方式:

  • 通过内存共享实现交互:线程间通过读写共享变量传递信息(Java 采用此方式)
  • 通过交互实现内存共享:线程间通过消息传递机制间接同步数据(如 Erlang 并发模型)
Java 选择内存共享模式,意味着所有线程操作的变量均存储在主内存中,线程自身持有变量的副本(位于 CPU 缓存或寄存器)。JMM 提供了 volatile、synchronized、final 三个关键关键字,分别针对并发编程的三大焦点问题:

  • 可见性:一个线程修改变量后,其他线程可否立即看到变化(volatile/synchronized/final)
  • 原子性:操作是否具备「不可分割」的特性(synchronized/CAS)
  • 有序性:指令实行顺序是否与代码编写顺序同等(volatile/synchronized)
而 CPU 缓存机制(数据副本不同等)与指令重排序(编译器 / 处理器优化导致顺序改变),是破坏数据可见性的两大焦点技术挑战。

重排序:编译器与处理器的「性能优化双刃剑」

无论是编译器还是处理器,都会对代码举行「性能优化」,其中「指令重排序」是重要手段 —— 在不改变程序最终效果的前提下,调整指令实行顺序以提高效率。

1. 编译器重排序


  • 发生阶段:编译期(javac 生成字节码时)
  • 优化手段:删除冗余指令、合并计算、调整循环内不变量(如将循环内的常量计算移到循环外)
  • 典范案例
    1. int a = 1;  
    2. int b = 2;  
    3. int c = a + b;  // 编译器可能重排为 b=2 → a=1 → c=3(结果不变)
    复制代码

2. 处理器重排序


  • 发生阶段:运行期(CPU 实行指令时)
  • 优化手段:利用流水线并行实行指令、缓冲写操作(Store Buffer)异步刷入主存
  • 典范影响
    若线程 A 先写变量 x 再写 y,CPU 大概因缓冲写优化,先写 y 再写 x,导致线程 B 读取时看到「y 已更新但 x 未更新」的中间状态。

伪共享:CPU 缓存行的「连带失效陷阱」

CPU 缓存以缓存行(Cache Line,通常 64 字节)为单位存储数据,缓存同等性协议(如 MESI)保证缓存与主存的数据同步,但一次失效操作会针对整个缓存行。

问题场景

假设变量 A(8 字节)与 B(8 字节)存储在同一缓存行:

  • 线程 1 修改 A,触发缓存行失效,线程 2 访问 B 时需从主存重新加载(即使 B 未被修改)
  • B 的缓存因「非自身原因」频繁失效,导致性能下降,即「伪共享」(False Sharing)。

解决方案


  • 空间换时间:让变量独占缓存行,避免其他变量与其共存。
  • Java 实现
    Java 1.8 引入 @sun.misc.Contended 注解,在变量或类级别添加填充字节:
    1. @sun.misc.Contended // 需加 JVM 参数 -XX:-RestrictContended 禁用限制
    2. class Counter {  
    3.     private volatile long value = 0;  // 自动填充至 64 字节,独占缓存行
    4. }
    复制代码
    该注解会在目标变量前后添加填凑数据,确保其单独占据一个缓存行,消除伪共享影响。

汇编指令 lock:CPU 硬件级的同步基石

lock 是 Intel CPU 提供的硬件级指令,用于解决多 CPU 环境下的缓存同等性问题,是 volatile 和 CAS 的底层实现底子。

焦点功能


  • 锁定范围进化

    • Pentium 及之前:锁定体系总线,阻止其他 CPU 访问内存(性能开销大)
    • 新架构(如 Core):锁定目标缓存行(Cache Line Locking),通过 MESI 协议广播变更,仅影响当前缓存行(高效)

  • 指令屏障作用:禁止 CPU 对 lock 指令前后的操作举行重排序(保障有序性)
  • 逼迫数据同步:将当前 CPU 缓存的数据刷入主存,并使其他 CPU 对应缓存行失效(保障可见性)

典范应用

lock 指令是 volatile 写操作的底层实现 —— 当线程写入 volatile 变量时,JIT 会生成 lock 前缀的汇编指令,确保数据立即同步到主存并通知其他线程。

汇编指令 cmpxchg:AQS 底层的「原子比较魔法」

cmpxchg(Compare and Exchange)是 CPU 提供的原子比较交换指令,Java 中的 Unsafe.compareAndSwapXXX 方法(如 compareAndSwapInt)依赖该指令实现。

指令逻辑
  1. cmpxchg target, expected_value, new_value  
  2. ; 若 target == expected_value,则设置 target = new_value,否则不改变  
复制代码
多 CPU 环境下,cmpxchg 会添加 lock 前缀,确保操作原子性:
  1. lock cmpxchg [address], eax  ; 锁定缓存行,保证比较-交换操作不可分割  
复制代码
在 AQS 中的作用

Java 并发包的焦点框架 AQS(AbstractQueuedSynchronizer),通过 cmpxchg 实现自旋锁:

  • 获取锁时,用 CAS 尝试修改状态(如 state 变量)
  • 释放锁时,通过 CAS 叫醒等待线程
    该机制确保 AQS 在获取 / 释放锁时具备内存可见性、原子性和有序性,是 ReentrantLock、Semaphore 等工具的底层支撑。

原子性操作 CAS:无锁编程的「高效与风险」

CAS(Compare-And-Swap,比较并交换)是实现无锁并发的焦点机制,通过 CPU 自旋实现原子操作,无需操作体系介入。

焦点流程


  • 读取当前值(V):获取共享变量的当前值
  • 比较期望值(A):判断当前值是否等于预期值
  • 交换新值(B):若相等则更新为新值,否则重试(自旋)

基于缓存锁定的原子性

CAS 通过「缓存锁定」(Cache Locking)保证原子性:

  • 写入数据后,通过 MESI 协议使其他 CPU 缓存的该变量失效
  • 后续读取时逼迫从主存加载最新值,确保数据同等性

三大焦点问题


  • ABA 问题

    • 场景:变量从 A→B→A,CAS 误认为未修改(如链表节点删除后重建)
    • 解决:引入版本号,使用 AtomicStampedReference(记录值 + 时间戳)

  • 单一变量限定

    • 局限:仅支持单个变量原子操作,多变量需封装为对象
    • 解决:用 AtomicReference 包裹对象,保证对象引用的原子性

  • 自旋开销

    • 风险:竞争激烈时,线程长时间自旋导致 CPU 占用率飙升
    • 优化:联合「自适应自旋」(JVM 动态调整自旋次数)或切换为锁机制


对象头:JVM 实现锁升级的「状态寄存器」

Java 对象头(Object Header)是 JVM 管理对象的焦点数据结构,64 位体系中占 16 字节(8 字节 MarkWord + 8 字节 Class Pointer),数组对象额外包含 4 字节数组长度。

焦点组成


  • MarkWord(8 字节)

    • 无锁状态:存储 HashCode(25 位)、分代年龄(4 位)、锁状态(1 位偏向锁标记 + 2 位锁状态)
    • 偏向锁:存储当前持有锁的线程 ID(54 位)、分代年龄(4 位)、锁状态(2 位)
    • 轻量级锁:存储指向线程栈中锁记录的指针(62 位)、锁状态(2 位)
    • 重量级锁:存储指向操作体系互斥锁的指针(62 位)、锁状态(2 位)

  • Class Pointer:指向对象的 Class 元数据,用于判断对象类型
  • Array Length(数组专有):记录数组长度,非数组对象无此字段

锁状态变化


  • 偏向锁(无竞争):首次加锁时记录线程 ID,后续直接复用(零开销)
  • 轻量级锁(轻度竞争):通过 CAS 自旋尝试获取锁,避免线程阻塞
  • 重量级锁(激烈竞争):自旋超时后升级为 OS 级锁,线程进入阻塞队列

volatile:轻量级可见性与有序性保障

volatile 是 Java 中轻量级的并发关键字,专门解决多线程环境下的变量可见性和指令重排序问题。

两大焦点保障


  • 编译期:插入内存屏障(Memory Barrier)

    • 写屏障:确保 volatile 写操作前的所有指令已实行完毕,且效果对后续操作可见
    • 读屏障:确保 volatile 读操作后的所有指令在读取之后实行
    • 禁止重排序:编译器无法调整 volatile 读写操作与其他指令的相对顺序

  • 运行期:CPU 级数据同步

    • 在 x86 架构下,JIT 会为 volatile 写操作生成 lock addl $0, (%esp) 指令(无实际计算,仅触发锁机制)
    • 锁缓存行:锁定目标缓存行,刷回主存并广播失效事件
    • 逼迫读取主存:其他线程检测到缓存失效后,必须从主存重新加载数据


典范使用场景


  • 状态标记变量:如 volatile boolean running = true;(线程安全的制止标记)
  • 单例模式 DCL 优化
    1. public class Singleton {  
    2.     private static volatile Singleton instance;  // 禁止指令重排序,避免返回未初始化对象  
    3.     // ...  
    4. }  
    复制代码
  • 注意:volatile 不保证原子性(如 i++ 需配合 AtomicInteger)

synchronized:从「偏向」到「重量级」的全链路锁升级

synchronized 是 JVM 内置的同步机制,通过「锁升级」计谋在不同竞争场景下动态调整锁状态,兼顾性能与精确性。

1. 偏向锁(无竞争场景)


  • 焦点逻辑:首次进入同步块时,在 MarkWord 中记录当前线程 ID,后续访问直接对比线程 ID(无需真实加锁)
  • 升级触发:当其他线程尝试获取锁时,通过 CAS 竞争失败,偏向锁升级为轻量级锁
  • 可见性保障:依赖 synchronized 块退出时的「隐式内存屏障」,逼迫革新主存数据

2. 轻量级锁(轻度竞争)


  • 焦点逻辑

    • 线程在栈中创建「锁记录」,存储对象头 MarkWord 的副本
    • 通过 CAS 将 MarkWord 替换为指向锁记录的指针(获取锁)
    • 竞争失败则自旋重试(默认自旋 10 次,JVM 可动态调整)

  • 优势:避免线程挂起的上下文切换开销,得当短时间竞争场景

3. 重量级锁(激烈竞争)


  • 焦点逻辑:自旋超时后,通过 park() 方法将线程挂起,放入操作体系的等待队列
  • 释放逻辑:解锁时调用 unpark() 叫醒线程,触发上下文切换(开销较大)
  • 实用场景:锁竞争激烈、持有锁时间较长的场景

锁优化最佳实践


  • 缩小同步范围:仅在须要代码块加锁(如用 synchronized(this) 替代 synchronized(class))
  • 联合显式锁:使用 ReentrantLock 的 tryLock() 避免永久阻塞

final:构造函数内的「重排序封印」

final 关键字不仅表现「变量不可变」,更在 JMM 层面提供了底层保障,确保其他线程不会读取到未初始化的 final 变量。

两大底层机制


  • 编译器束缚

    • 禁止将 final 变量的初始化操作移到构造函数之外
    • 非 final 变量大概在构造函数外初始化(如默认值初始化)

  • 处理器束缚

    • 构造函数结束前,禁止将对象引用(this)暴露给其他线程
    • 通过内存屏障,确保 final 变量赋值操作在构造函数内完成


反模式警示
  1. public class FinalProblem {  
  2.     final int x;  
  3.     static FinalProblem instance;  
  4.     public FinalProblem() {  
  5.         x = 10;  
  6.         instance = this;  // 危险!若此时被其他线程读取,x 可能未初始化(JMM 禁止此行为)  
  7.     }  
  8. }  
复制代码
JMM 保证:只要通过正常构造函数初始化,其他线程看到的 final 变量肯定是已赋值的状态,避免「半初始化对象」问题。

总结:JMM 焦点技术图谱

JMM 三大焦点问题:
可见性 → volatile(内存屏障 + lock 指令)、synchronized(锁释放 / 获取的内存语义)、final(构造函数内初始化)
原子性 → synchronized(互斥锁)、CAS(cmpxchg 指令 + 自旋)
有序性 → volatile(禁止重排序)、synchronized(管程进入 / 退出的有序性)
底层硬件机制:
CPU 缓存 → 伪共享(@Contended 注解填充缓存行)
指令重排序 → 编译器重排序(优化代码)+ 处理器重排序(流水线优化)
汇编指令 → lock(缓存行锁定,保障可见性 + 有序性)、cmpxchg(CAS 原子操作)
关键字对比:
关键字可见性原子性有序性锁机制实用场景volatile✅❌✅无状态标记、轻量同步synchronized✅✅✅锁升级临界区保护final✅—✅无不可变变量并发工具底层:
AQS → CAS(cmpxchg)+ volatile(state 变量)+ 双向链表(等待队列)
原子类 → Unsafe.compareAndSwapXXX(底层 cmpxchg 指令)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

曹旭辉

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表