核心概念
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=true→a=1,线程2可能看到flag=true但a=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 happens-before 操作2(程序顺序规则)
- 操作2 happens-before 操作3(volatile规则)
- 操作3 happens-before 操作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();
}
面试总结
核心要点
- as-if-serial:单线程优化规则,保证结果不变
- happens-before:多线程同步规则,保证可见性和有序性
- 联系:互补关系,共同构成JMM语义
- 区别:适用场景、约束范围、保证内容不同
答题模板
简明版:
- as-if-serial是单线程内的重排序规则,保证执行结果不变
- happens-before是多线程间的可见性规则,保证操作的可见性和顺序
- 前者关注单线程正确性,后者关注多线程同步
完整版:
- 定义对比:单线程 vs 多线程、结果不变 vs 可见性保证
- 举例说明:as-if-serial允许无依赖操作重排序,happens-before通过volatile等禁止特定重排序
- 联系:as-if-serial是基础,happens-before是额外约束
- 实践:DCL单例中,volatile同时利用了两个原则
关键对比表
// as-if-serial示例
int a = 1; // 可以重排序
int b = 2; // 可以重排序
// 单线程内随意优化
// happens-before示例
a = 1;
flag = true; // volatile写,禁止之前的操作重排到之后
// 多线程必须保证可见性