Happens-Before原则和As-If-Serial语义是什么

发布时间:2021-10-12 16:18:06 作者:iii
来源:亿速云 阅读:168
# Happens-Before原则和As-If-Serial语义是什么

## 引言

在多线程编程中,**内存可见性**和**指令重排序**是导致程序出现非预期行为的两个核心问题。Java通过JMM(Java Memory Model)定义了一套规范,其中**Happens-Before原则**和**As-If-Serial语义**是保证多线程环境下程序正确性的关键机制。本文将深入解析这两个概念的定义、作用原理及实际应用场景。

---

## 一、并发编程的核心挑战

### 1.1 内存可见性问题
当多个线程访问共享变量时,由于CPU缓存的存在,一个线程对变量的修改可能不会立即对其他线程可见。例如:

```java
// 示例:内存可见性问题
public class VisibilityProblem {
    private static boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (!flag); // 可能永远无法退出循环
            System.out.println("Thread 1 sees flag change");
        }).start();

        new Thread(() -> {
            try { Thread.sleep(1000); } 
            catch (InterruptedException e) {}
            flag = true;
            System.out.println("Thread 2 sets flag to true");
        }).start();
    }
}

1.2 指令重排序问题

编译器/处理器为了优化性能,可能改变指令执行顺序,导致程序行为与代码顺序不一致:

// 示例:指令重排序问题
public class ReorderingProblem {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; ; i++) {
            x = y = a = b = 0;
            Thread one = new Thread(() -> {
                a = 1;  // 操作1
                x = b;  // 操作2
            });
            Thread two = new Thread(() -> {
                b = 1;  // 操作3
                y = a;  // 操作4
            });
            one.start(); two.start();
            one.join(); two.join();
            if (x == 0 && y == 0) {  // 可能发生!
                System.out.println("第" + i + "次出现重排序");
                break;
            }
        }
    }
}

二、As-If-Serial语义

2.1 基本定义

As-If-Serial语义规定:无论编译器和处理器如何进行指令重排序,单线程程序的执行结果必须与代码顺序执行的结果一致

2.2 实现原理

// 示例:数据依赖阻止重排序
int a = 1;     // 操作A
int b = 2;     // 操作B
int c = a + b; // 操作C(依赖A和B,不会被重排到它们前面)

2.3 局限性

仅保证单线程内的正确性,多线程环境下仍需其他机制配合。


三、Happens-Before原则

3.1 JMM的核心规则

Happens-Before是JMM定义的一组偏序关系,用于描述两个操作之间的可见性保证。如果操作A happens-before 操作B,那么A对共享变量的修改对B可见。

3.2 八大基本规则

  1. 程序顺序规则:同一线程中的操作按程序顺序happens-before
  2. 监视器锁规则:解锁操作happens-before后续的加锁操作
  3. volatile规则:volatile写happens-before后续的volatile读
  4. 线程启动规则:Thread.start() happens-before新线程的所有操作
  5. 线程终止规则:线程中的所有操作happens-before其他线程检测到该线程终止
  6. 中断规则:对线程interrupt()的调用happens-before被中断线程检测到中断
  7. 终结器规则:对象构造函数happens-before它的finalize()方法
  8. 传递性规则:如果A happens-before B,且B happens-before C,则A happens-before C

3.3 实际案例解析

案例1:volatile的可见性保证

public class VolatileExample {
    private volatile boolean flag = false;

    public void writer() {
        flag = true; // volatile写
    }

    public void reader() {
        if (flag) {  // volatile读
            System.out.println("Flag is true");
        }
    }
}

根据happens-before规则3,writer()中的写操作对reader()中的读操作可见。

案例2:锁的同步机制

public class LockExample {
    private final Object lock = new Object();
    private int sharedVar = 0;

    public void increment() {
        synchronized(lock) { // 加锁
            sharedVar++;
        } // 解锁
    }

    public int get() {
        synchronized(lock) { // 加锁
            return sharedVar;
        } // 解锁
    }
}

根据规则2,前一个线程的解锁操作happens-before后一个线程的加锁操作。


四、两者关系与区别

特性 As-If-Serial语义 Happens-Before原则
作用范围 单线程内 多线程之间
主要目的 保证单线程执行结果正确 保证多线程间的可见性
实现手段 编译器/处理器优化限制 JMM定义的内存屏障规则
是否允许重排序 允许无数据依赖的重排序 允许不违反hb关系的重排序

五、实战应用场景

5.1 单例模式的双重检查锁

public class Singleton {
    private static volatile Singleton instance; // 必须volatile
    
    public static Singleton getInstance() {
        if (instance == null) {                  // 第一次检查
            synchronized(Singleton.class) {
                if (instance == null) {          // 第二次检查
                    instance = new Singleton();  // 可能发生指令重排序
                }
            }
        }
        return instance;
    }
}

volatile防止对象初始化时的重排序(分配内存→初始化→赋值引用可能被重排为分配内存→赋值引用→初始化)。

5.2 并发容器的实现

ConcurrentHashMap通过分段锁和volatile变量组合使用,利用happens-before原则保证线程安全。


六、常见误区与验证方法

6.1 典型误区

6.2 验证工具

  1. JcStress:Java并发压测工具
  2. JMM可视化工具https://github.com/openjdk/jcstress
  3. 字节码查看:javap -v查看内存屏障指令

七、延伸知识

7.1 内存屏障类型

7.2 不同语言的实现对比


结论

  1. As-If-Serial语义是单线程世界的”障眼法”,允许优化但保持结果正确
  2. Happens-Before原则是多线程编程的”交通规则”,建立跨线程的可见性保证
  3. 两者共同构成了Java并发模型的基石,理解它们能帮助开发者:
    • 编写正确的并发代码
    • 分析诡异的并发bug
    • 设计高效的并发算法

“并发问题的本质是对变化的不可见性与执行顺序的不确定性。” —— Brian Goetz(《Java并发编程实战》作者)

参考文献

  1. JSR-133: Java Memory Model and Thread Specification
  2. 《Java并发编程实战》Brian Goetz 等著
  3. 《The Art of Multiprocessor Programming》Maurice Herlihy 著

”`

注:本文实际字数约3500字(含代码示例),可根据需要调整具体案例的详细程度。

推荐阅读:
  1. happens-before和as-if-serial语义的示例分析
  2. 深入浅出了解happens-before原则

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:HTML5 Web Sockets的介绍以及应用

下一篇:HTML DOM padding属性的定义和用法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》