Skip to content
清晨的一缕阳光
返回

Java 内存模型进阶

Java 内存模型进阶

Java 内存模型(JMM)是并发编程的理论基础,理解 JMM 有助于编写正确的并发代码。

一、happens-before 规则

1.1 规则详解

// 1. 程序次序规则
// 同一线程内,按代码顺序执行
a = 1;  // happens-before
b = 2;

// 2. 锁规则
// 解锁 happens-before 后续加锁
synchronized (lock) {
    x = 1;
}
// x = 1 的结果对后续获取锁的线程可见

// 3. volatile 规则
// volatile 写 happens-before 后续 volatile 读
volatile boolean flag = false;

// 线程 A
flag = true;  // volatile 写

// 线程 B
if (flag) {   // volatile 读
    // 能看到线程 A 的所有修改
}

// 4. 传递性规则
// A happens-before B, B happens-before C
// 则 A happens-before C

1.2 实战应用

// 双重检查锁定(DCL)
public class Singleton {
    private volatile static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    // volatile 保证:
                    // 1. 禁止指令重排
                    // 2. 写操作对其他线程立即可见
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

二、内存屏障

2.1 屏障类型

LoadLoad 屏障:
  Load1; LoadLoad; Load2
  保证 Load1 在 Load2 之前完成

StoreStore 屏障:
  Store1; StoreStore; Store2
  保证 Store1 在 Store2 之前刷新

LoadStore 屏障:
  Load1; LoadStore; Store2
  保证 Load1 在 Store2 之前完成

StoreLoad 屏障:
  Store1; StoreLoad; Load2
  保证 Store1 在 Load2 之前刷新(最严格)

2.2 volatile 实现

// HotSpot 实现
// volatile 写操作
public void putVolatile(Object value) {
    // 1. 写入值
    this.value = value;
    
    // 2. 插入 StoreStore 屏障
    UNSAFE.storeFence();
    
    // 3. 插入 StoreLoad 屏障
    UNSAFE.fullFence();
}

// volatile 读操作
public Object getVolatile() {
    // 1. 读取值
    Object value = this.value;
    
    // 2. 插入 LoadLoad 屏障
    UNSAFE.loadFence();
    
    return value;
}

三、原子性保证

3.1 64 位变量原子性

// long 和 double 是非原子的(32 位 JVM)
private long count;  // 读写可能不是原子操作

// 解决方案
// 1. 使用 volatile
private volatile long count;

// 2. 使用 AtomicLong
private AtomicLong count = new AtomicLong(0);

// 3. 使用锁
private final Object lock = new Object();
public void increment() {
    synchronized (lock) {
        count++;
    }
}

3.2 复合操作原子性

// ❌ 非原子操作
private int count = 0;
public void increment() {
    count++;  // 包含读 - 改 - 写三个操作
}

// ✅ 原子操作
// 1. synchronized
public synchronized void increment() {
    count++;
}

// 2. Atomic
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();
}

// 3. Lock
private final Lock lock = new ReentrantLock();
public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

四、指令重排

4.1 重排示例

// 经典重排问题
class ReorderExample {
    int a = 0;
    boolean flag = false;
    
    // 线程 A
    public void writer() {
        a = 1;           // 1
        flag = true;     // 2
    }
    
    // 线程 B
    public void reader() {
        if (flag) {      // 3
            int i = a;   // 4
        }
    }
}

// 可能重排为:2 → 1 → 3 → 4
// 线程 B 看到 flag=true,但 a=0

// 解决方案:使用 volatile
volatile boolean flag = false;
// 禁止 1 和 2 重排

4.2 防止重排

// 使用 volatile 防止重排
private volatile int x = 0;
private volatile int y = 0;

public void write() {
    x = 1;      // 不会被重排到后面
    y = 2;      // 不会被重排到前面
}

// 使用 Lock 防止重排
private final Lock lock = new ReentrantLock();
public void write() {
    lock.lock();
    try {
        x = 1;  // 锁保证有序性
        y = 2;
    } finally {
        lock.unlock();
    }
}

五、总结

JMM 核心要点:

规则说明应用场景
程序次序线程内顺序执行单线程场景
锁规则解锁→加锁可见synchronized
volatile写→读可见状态标志
传递性A→B, B→C 则 A→C链式同步

理解 JMM 的 happens-before 规则和内存屏障,是编写正确并发代码的基础。


分享这篇文章到:

上一篇文章
性能优化实战
下一篇文章
Spring Boot Prometheus + Grafana 监控