JVM并发编程:从JMM到底层硬件的深度剖析

165 阅读3分钟

一、Java内存模型(JMM):多线程通信的规则

JMM 是一种抽象的概念,它定义了线程和主内存之间的交互规则。

  • 主内存(Main Memory) :所有线程共享的内存区域,对应 Java 中的堆内存
  • 工作内存(Working Memory) :每个线程私有的内存区域,对应 CPU 中的缓存(L1, L2)寄存器

三大特性

  1. 原子性(Atomicity) :一个操作是不可分割的。
  2. 可见性(Visibility) :一个线程对共享变量的修改,对其他线程是立即可见的。
  3. 有序性(Ordering) :代码的执行顺序,与书写顺序一致。

二、硬件层支持:CPU缓存与内存屏障

JMM 的实现依赖于底层硬件。理解 CPU 的缓存结构和内存屏障是理解 JMM 的关键。

  • CPU 缓存:CPU 缓存是线程私有的,这导致了可见性问题
  • 内存屏障(Memory Barrier) :一种CPU指令,用于强制阻止编译器和处理器对指令进行重排序,并刷新 CPU 缓存,从而解决可见性和有序性问题

三、volatile的底层实现:内存屏障与可见性

volatile 关键字能够保证变量的可见性有序性,其底层原理是插入内存屏障。

  • 写操作:在 volatile 变量的写操作之后,会插入一个 StoreLoad 内存屏障。这个屏障的作用是:

    • 强制将线程工作内存中的值写回主内存。
    • 使其他线程的缓存行无效,从而强制它们从主内存中重新读取。
  • 读操作:在 volatile 变量的读操作之前,会插入一个 LoadLoad 屏障。这保证了读操作总能获取到最新的值。


四、synchronized的底层实现:锁升级与互斥

synchronized 关键字是 JVM 提供的重量级同步机制,其底层实现是基于**对象头(Mark Word)**中的锁状态。

  • 锁升级路径

    1. 无锁:对象创建时的初始状态。
    2. 偏向锁(Biased Locking) :当只有一个线程访问同步代码块时,JVM 会给对象头写入该线程的 ID,无需额外的同步开销。
    3. 轻量级锁(Lightweight Locking) :当出现竞争时,偏向锁会升级为轻量级锁。通过 CAS 操作在栈帧中创建锁记录。
    4. 重量级锁(Heavyweight Locking) :当轻量级锁的 CAS 操作失败,即出现激烈竞争时,锁会膨胀为重量级锁,进入操作系统级别的互斥,导致线程阻塞和唤醒的开销。

五、happens-before原则:JMM的最终保证

happens-before 原则定义了操作之间的顺序和可见性。如果操作 A happens-before 操作 B,那么 A 的结果对 B 是可见的。

  • 关键规则

    • 程序顺序规则:单线程内,代码书写顺序即为执行顺序。
    • 锁规则:一个线程的解锁操作 happens-before 另一个线程的加锁操作。
    • volatile 变量规则:对一个 volatile 变量的写操作 happens-before 任何后续对它的读操作。

六、总结:JMM与并发编程的实践

  • volatile:适用于一写多读的场景,能够保证可见性和有序性,但无法保证原子性。
  • synchronized:能够同时保证原子性、可见性、有序性,但性能开销较大。
  • happens-before:是理解并发编程的基石,它为我们提供了强大的内存可见性保证