2.Java 内存模型(JMM)
·
2.Java 内存模型(JMM)
- 一、JMM 的核心概念
- JMM(Java Memory Model,Java 内存模型) 是 Java 虚拟机(JVM)规范中定义的一种抽象模型,用于描述 多线程 环境下, 线程如何与主内存(Main Memory)以及工作内存(Working Memory)交互 ,以及如何保证内存的 可见性 、 有序性 和 原子性 。
- 1. 主内存与工作内存
- 主内存(Main Memory) :
- 所有线程 共享 的内存区域,存储变量(实例字段、静态字段等)。
- 工作内存(Working Memory) :
- 每个线程 私有 的内存区域,存储线程使用的变量的副本。
- 线程对变量的操作(读取、赋值等)都在工作内存中进行。
- 主内存(Main Memory) :
- 2. 内存交互操作
- (1)lock(锁定) :作用于主内存变量,标识为线程独占。
- (2)unlock(解锁) :作用于主内存变量,释放锁定状态。
- (3)read(读取) :从主内存读取变量到工作内存。
- (4)load(载入) :将 read 操作读取的值放入工作内存的变量副本。
- (5)use(使用) :将工作内存中的变量值传递给执行引擎。
- (6)assign(赋值) :将执行引擎计算的结果赋值给工作内存中的变量。
- (7)store(存储) :将工作内存中的变量值传递到主内存。
- (8)write(写入) :将 store 操作传递的值写入主内存变量。
- 3. 内存交互规则
- (1)顺序性 :read/load 和 store/write 必须成对出现,且顺序不能打乱。
- (2)可见性 :变量修改后必须同步到主内存,读取时必须从主内存刷新。
- (3)原子性 :对基本数据类型的操作是原子的(long 和 double 除外)。
- 二、JMM 的三大特性
- 1. 原子性(Atomicity)
- 一个操作要么全部执行,要么全部不执行。
- 对于 long 和 double,64 位数据类型的操作可能被拆分为两个 32 位操作,因此需要
volatile关键字保证原子性。 - JMM 保证对基本数据类型的操作是原子的(如 int、boolean 等)。
- 1. 原子性(Atomicity)
- 示例1:使用
**synchronized**保证原子性
class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 确保 count++ 是原子操作
}
public int getCount() {
return count;
}
}
- 示例2:使用
**AtomicInteger**保证原子性
import java.util.concurrent.atomic.AtomicInteger;
class AtomicExample {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
count.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
count.incrementAndGet();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终 count 值:" + count); // 结果必然是 2000
}
}
- AtomicInteger 基于 CAS(Compare-And-Swap) 实现无锁操作,能够保证原子性。但它可能会引发 ABA 问题 :即变量从 A 改为 B,又改回 A,CAS 检查发现值没变,但实际变量经历了状态变化。
- 解决ABA问题: 1、增加版本号:每次更新变量时,同时更新版本号;2、使用 AtomicStampedReference。- 2. 可见性(Visibility)
- 一个线程对共享变量的修改,其他线程能够立即看到。
- 实现可见性的方式:
**volatile 关键字**:强制将修改的值刷新到主内存,并强制从主内存读取最新值。**synchronized 关键字**:在解锁前将变量同步到主内存。**final 关键字**:确保变量在构造完成后对其他线程可见。- 示例:使用 volatile 保证可见性
- 2. 可见性(Visibility)
class SharedData {
private volatile boolean flag = false;
public void setFlagTrue() {
flag = true; // 线程 A 设置 flag 为 true
}
public boolean isFlagTrue() {
return flag; // 线程 B 读取 flag
}
}
- 3. 有序性(Ordering)
- 程序执行的顺序按照代码的先后顺序执行。
- JMM 通过 happens-before 规则 保证有序性。
- 指令重排序 :
- 编译器和处理器可能会对指令进行重排序以优化性能。
- JMM 通过内存屏障(Memory Barrier)禁止某些重排序。- 示例:使用 volatile 保证有序性
class FlagChecker {
private volatile boolean flag = false;
public void setFlagTrue() {
flag = true; // 线程 A 设置 flag
}
public boolean isFlagTrue() {
return flag; // 线程 B 读取 flag
}
}
- 三、happens-before 规则
- happens-before ( 先行发生原则 )是 JMM 的核心规则,用于定义操作之间的可见性和顺序性。以下是常见的 happens-before 规则:
- 程序顺序规则 :
- 一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
**volatile**规则 :- 对一个 volatile 变量的写操作,happens-before 于后续对这个变量的读操作。
- 锁规则 :
- 对一个锁的解锁操作,happens-before 于后续对这个锁的加锁操作。
- 线程启动规则 :
- 线程的 start() 方法 happens-before 于该线程的任何操作。
- 线程终止规则 :
- 线程中的所有操作 happens-before 于其他线程检测到该线程已经终止。
- 传递性规则 :
- 如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
- 示例
class Example {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42; // 1
v = true; // 2
}
public void reader() {
if (v) { // 3
System.out.println(x); // 4
}
}
}
- 操作 1 happens-before 操作 2(程序顺序规则)。操作 2 happens-before 操作 3(volatile 变量规则)。根据传递性,操作 1 happens-before 操作 4,因此线程 B 读取 x 时能看到线程 A 写入的值 42。
- 四、
**volatile**关键字- 1. 作用
- 保证变量的可见性。
- 禁止指令重排序。
- 2. 实现原理
- 内存屏障(Memory Barrier) :
- 写操作后插入 StoreStore 屏障和 StoreLoad 屏障。
- 读操作前插入 LoadLoad 屏障和 LoadStore 屏障。
- 强制刷新主内存 :
- 写操作后,强制将工作内存中的值刷新到主内存。
- 读操作前,强制从主内存读取最新值。
- 内存屏障(Memory Barrier) :
- 3. 使用场景
- 状态标志(如 boolean flag)。
- 单例模式的双重检查锁(Double-Checked Locking)。
- 示例:双重检查锁定
- 1. 作用
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 保证对象初始化的原子性
}
}
}
return instance;
}
}
- 五、
**synchronized**关键字- 1. 作用
- 保证原子性、可见性和有序性。
- 通过锁机制实现线程同步。
- 2. 实现原理
- 对象头 :
- 每个对象都有一个对象头,包含锁信息。
- Monitor 机制 :
- 通过 monitorenter 和 monitorexit 指令实现锁的获取和释放。
- 对象头 :
- 3. 使用场景
- 方法同步。
- 代码块同步。
- 1. 作用
- 六、
**final**关键字- 1. 作用
- 保证变量的不可变性。
- 确保变量在构造完成后对其他线程可见。
- 2. 实现原理
- 禁止指令重排序。
- 在构造函数中完成初始化后,才能被其他线程访问。
- 1. 作用
- 七、JMM 与硬件内存模型
- 1. 缓存一致性
- 现代 CPU 使用多级缓存(L1、L2、L3)来加速内存访问。
- JMM 通过内存屏障保证缓存一致性。
- 2. 内存屏障
- LoadLoad :禁止读操作重排序。
- StoreStore :禁止写操作重排序。
- LoadStore :禁止读操作与写操作重排序。
- StoreLoad :禁止写操作与读操作重排序。
- 1. 缓存一致性
- 八、JMM 的实践与调优
-
- 避免竞态条件
- 使用 synchronized 或 volatile 保证线程安全。
- 使用原子类(如 AtomicInteger)实现无锁编程。
-
- 性能优化
- 减少锁的粒度。
- 使用无锁数据结构(如 ConcurrentHashMap)。
-
- 调试工具
- JConsole :监控线程状态。
- VisualVM :分析线程堆栈。
- Arthas :动态诊断线程问题。
-
更多推荐


所有评论(0)