核心概念

as-if-serial(似乎串行)

as-if-serial 语义保证:在单线程环境下,无论如何进行指令重排序,程序的执行结果不能改变。编译器和处理器可以自由优化,但必须保证最终结果与按代码顺序执行的结果一致。

happens-before(先行发生)

happens-before 原则保证:在多线程环境下,如果操作A happens-before 操作B,那么A的执行结果对B可见,且A的执行顺序排在B之前。


核心区别

维度 as-if-serial happens-before
适用场景 单线程 多线程
保证内容 执行结果不变 可见性和有序性
限制范围 有数据依赖的操作不能重排序 符合8大规则的操作不能重排序
目标 允许最大化性能优化 保证并发程序正确性
关注点 单线程内的正确性 多线程间的可见性

详细对比

1. as-if-serial:单线程优化

在单线程中,编译器和处理器可以随意重排序没有数据依赖的操作。

// 单线程示例
public void singleThread() {
    int a = 1;  // 操作1
    int b = 2;  // 操作2
    int c = a + 1;  // 操作3
    int d = b * 2;  // 操作4
}

// 可能的执行顺序(as-if-serial允许):
// 1. 1 → 2 → 3 → 4(原始顺序)
// 2. 2 → 1 → 4 → 3(重排序)
// 3. 1 → 3 → 2 → 4(重排序)

// 不允许的重排序:
// 3 → 1 → 2 → 4(违反数据依赖,c依赖a)

数据依赖示例

int a = 1;      // 操作1
int b = a + 1;  // 操作2:依赖操作1
int c = b * 2;  // 操作3:依赖操作2

// as-if-serial保证:1 → 2 → 3的执行顺序不能改变
// 因为存在数据依赖:b依赖a,c依赖b

2. happens-before:多线程同步

在多线程中,as-if-serial无法保证线程安全,需要happens-before规则。

// 多线程示例:as-if-serial不够用
public class Reordering {
    private int a = 0;
    private boolean flag = false;
    
    // 线程1
    public void writer() {
        a = 1;           // 操作1
        flag = true;     // 操作2
    }
    
    // 线程2
    public void reader() {
        if (flag) {      // 操作3
            int i = a;   // 操作4:可能看到a=0!
        }
    }
}

问题分析

  • 在线程1中,操作1和操作2没有数据依赖,as-if-serial允许重排序
  • 如果重排序为:flag=truea=1,线程2可能看到flag=truea=0
  • as-if-serial只保证单线程正确性,不保证多线程可见性

使用happens-before解决

public class ReorderingSolution {
    private int a = 0;
    private volatile boolean flag = false;  // 使用volatile
    
    // 线程1
    public void writer() {
        a = 1;           // 操作1
        flag = true;     // 操作2:volatile写
    }
    
    // 线程2
    public void reader() {
        if (flag) {      // 操作3:volatile读
            int i = a;   // 操作4:一定看到a=1
        }
    }
}

happens-before保证

  1. 操作1 happens-before 操作2(程序顺序规则)
  2. 操作2 happens-before 操作3(volatile规则)
  3. 操作3 happens-before 操作4(程序顺序规则)
  4. 传递性:操作1 happens-before 操作4

内在联系

1. 互补关系

  • as-if-serial 是JMM在单线程内遵循的基本原则
  • happens-before 是JMM在多线程间遵循的同步原则
  • 两者共同构成了JMM的完整语义
// 单线程 + 多线程的综合示例
public class CombinedExample {
    private int x = 0;
    private volatile int y = 0;
    
    // 线程1
    public void thread1() {
        int a = 1;      // A:单线程操作
        int b = 2;      // B:单线程操作
        x = a + b;      // C:单线程操作
        y = 1;          // D:volatile写
    }
    
    // 线程2
    public void thread2() {
        if (y == 1) {   // E:volatile读
            int temp = x;  // F:能看到x=3
        }
    }
}

// as-if-serial:A、B、C在线程1内可以重排序(前提是不改变C的结果)
// happens-before:D happens-before E,保证线程2能看到x的最新值

2. 层次关系

  • as-if-serial 是编译器和处理器优化的基础规则
  • happens-before 是在as-if-serial基础上,为多线程增加的额外约束
// 层次关系示意
单线程as-if-serial基础层
         
多线程as-if-serial + happens-before约束层
         
最终结果正确的并发程序

3. 重排序约束范围

public class ReorderingConstraints {
    private int a = 0, b = 0;
    private volatile int v = 0;
    
    public void example() {
        // === 单线程约束(as-if-serial) ===
        int x = 1;      // 1
        int y = 2;      // 2
        int z = x + y;  // 3:依赖1和2,不能重排到它们之前
        
        // === 多线程约束(happens-before) ===
        a = 1;          // 4:可以与5重排序(单线程视角)
        b = 2;          // 5:可以与4重排序(单线程视角)
        v = 1;          // 6:volatile写,4和5不能重排到6之后(多线程视角)
    }
}

as-if-serial允许:操作4和操作5重排序(单线程内无依赖)

happens-before禁止:操作4、5不能重排到操作6之后(volatile规则)


实践场景

场景1:DCL单例模式

public class Singleton {
    private static volatile Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {  // 检查1
            synchronized (Singleton.class) {
                if (instance == null) {  // 检查2
                    // === 创建对象的三步 ===
                    // 1. 分配内存
                    // 2. 初始化对象
                    // 3. instance指向内存
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

分析

  • as-if-serial视角:步骤1、2、3在单线程内可以重排序(2和3可能交换)
  • happens-before视角:volatile写禁止重排序,保证其他线程看到完整初始化的对象
  • 结论:必须用volatile,仅依赖as-if-serial无法保证多线程安全

场景2:计数器

// 不安全的计数器
public class UnsafeCounter {
    private int count = 0;
    
    public void increment() {
        // count++分解为三步:
        // 1. 读取count
        // 2. count+1
        // 3. 写回count
        count++;
    }
}

// as-if-serial保证:单线程调用increment()结果正确
// happens-before不保证:多线程调用可能丢失更新

解决方案

// 方案1:synchronized(happens-before锁规则)
public synchronized void increment() {
    count++;
}

// 方案2:AtomicInteger(CAS + volatile)
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();
}

面试总结

核心要点

  1. as-if-serial:单线程优化规则,保证结果不变
  2. happens-before:多线程同步规则,保证可见性和有序性
  3. 联系:互补关系,共同构成JMM语义
  4. 区别:适用场景、约束范围、保证内容不同

答题模板

简明版

  • as-if-serial是单线程内的重排序规则,保证执行结果不变
  • happens-before是多线程间的可见性规则,保证操作的可见性和顺序
  • 前者关注单线程正确性,后者关注多线程同步

完整版

  1. 定义对比:单线程 vs 多线程、结果不变 vs 可见性保证
  2. 举例说明:as-if-serial允许无依赖操作重排序,happens-before通过volatile等禁止特定重排序
  3. 联系:as-if-serial是基础,happens-before是额外约束
  4. 实践:DCL单例中,volatile同时利用了两个原则

关键对比表

// as-if-serial示例
int a = 1;  // 可以重排序
int b = 2;  // 可以重排序
// 单线程内随意优化

// happens-before示例  
a = 1;
flag = true;  // volatile写,禁止之前的操作重排到之后
// 多线程必须保证可见性