本文主要是介绍happens-before 关系,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
2、happens-before 关系
在 Java 中,volatile
关键字用于变量的修饰,它确保对该变量的所有读写操作都是直接从主内存中进行的,而不是从线程的本地缓存
中读取。volatile
关键字可以保证某些类型的内存可见性,并在一定程度上防止指令重排序。具体来说,volatile
可以建立一种特殊
的 happens-before 关系,确保多线程程序的正确性和一致性。
happens-before 关系是 Java 内存模型(JMM)中的一种重要概念,用于定义线程之间操作的顺序性。简单来说,如果操作 A happens-
before 操作 B,那么操作 A 的结果对操作 B 是可见的,并且操作 A 的顺序在操作 B 之前。
对于 volatile
变量,有以下几种 happens-before 关系:
- 对
volatile
变量的写操作 happens-before 随后的读操作:- 如果线程 A 对一个
volatile
变量进行写操作,然后线程 B 对这个volatile
变量进行读操作,那么在 A 线程中对这volatile
变量的写操作 happens-before B 线程中的读操作。这意味着线程 B 将看到线程 A 写入的最新值。
- 如果线程 A 对一个
- 对
volatile
变量的写操作会禁止写之前的所有操作被重排序到写操作之后:- 在对
volatile
变量进行写操作之前的所有操作,在内存模型上会被“刷回”主内存。即对volatile
变量的写操作之前的所有普通变量的操作都将在写操作之前完成,并且在写操作之前的所有操作对后续的任何线程都是可见的。
- 在对
- 对
volatile
变量的读操作会禁止读之后的所有操作被重排序到读操作之前:- 在对
volatile
变量进行读操作之后的所有操作,在内存模型上会从主内存读取最新值。即对volatile
变量的读操作之后的所有普通变量的操作都将在读操作之后完成,并且在读操作之后的所有操作将看到写操作之后的最新结果。
- 在对
示例代码
以下是一个简单的代码示例,展示了 volatile
的 happens-before 关系:
public class VolatileHappensBeforeExample {
// private volatile boolean flag = false;private boolean flag = false;private int counter = 0;public void writer() {counter = 1; // 普通写操作flag = true; // volatile 写操作}public void reader() {if (flag) { // volatile 读操作System.out.println(counter); // 普通读操作}}public static void main(String[] args) {VolatileHappensBeforeExample example = new VolatileHappensBeforeExample();for (int i = 0; i < 10; i++) {// 创建写线程Thread writerThread = new Thread(() -> {example.writer();});// 创建读线程Thread readerThread = new Thread(() -> {example.reader();});writerThread.start();readerThread.start();}}
}
在这个示例中:
writer()
方法中,对flag
的写操作 happens-before 随后的reader()
方法中对flag
的读操作。- 因此,如果
reader()
方法检测到flag
为true
,则它必然会看到counter
的值为 1(即writer()
方法中的写操作已发生)。
这种 happens-before 关系确保了多线程环境中的变量更新对于其他线程是可见的,从而保证了线程之间的正确通信。
上面这么啰里巴嗦地讲,这也太抽象了,即使去掉 volatile 修饰其实也不一定会出现打印不出来1的情况,必须整个程序验证一下。
验证 happens-before 关系
验证不使用 volatile
关键字会导致错误,可以通过编写一个多线程测试程序,观察在不同线程之间的共享变量是否会出现不可见性问题。具体来说,可以通过运行代码并检测在某些情况下是否会出现预期之外的结果,例如永远不会打印出预期的值
要验证不使用 volatile
关键字会导致错误,可以通过编写一个多线程测试程序,观察在不同线程之间的共享变量是否会出现不可见性问题。具体来说,可以通过运行代码并检测在某些情况下是否会出现预期之外的结果,例如永远不会打印出预期的值。
验证代码
以下是一个示例代码,通过多个线程的交互来验证如果不使用 volatile
关键字会出现的问题:
public class VolatileHappensBeforeExample {
// private boolean flag = false; // 没有使用 volatileprivate volatile boolean flag = false; // 使用 volatileprivate int counter = 0;public void writer() {counter = 1; // 普通写操作flag = true; // 普通写操作System.out.println(Thread.currentThread().getName() + " set flag to true");}public void reader() {while (!flag) {// Busy-wait loop, waiting for flag to become true}System.out.println(Thread.currentThread().getName() + " sees flag is true and counter is " + counter);}public static void main(String[] args) {VolatileHappensBeforeExample example = new VolatileHappensBeforeExample();// 创建写线程Thread writerThread = new Thread(() -> {try {Thread.sleep(100); // 确保 reader 线程先启动} catch (InterruptedException e) {Thread.currentThread().interrupt();}example.writer();}, "WriterThread");// 创建读线程Thread readerThread = new Thread(() -> {example.reader();}, "ReaderThread");readerThread.start();writerThread.start();try {readerThread.join();writerThread.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}
代码解释
- writer() 方法:
- 设置
counter
为 1。 - 设置
flag
为 true。 - 打印当前线程名称及其操作。
- 设置
- reader() 方法:
- 使用一个 busy-wait 循环等待
flag
变为 true。 - 当
flag
为 true 时,打印当前线程名称及其看到的counter
值。
- 使用一个 busy-wait 循环等待
- main 方法:
- 创建并启动
writerThread
和readerThread
。 - 使用
Thread.sleep(100)
确保readerThread
先启动。
- 创建并启动
可能的结果
运行上述代码多次,可能会看到以下结果:
- 有时,程序会如预期输出
WriterThread set flag to true
和ReaderThread sees flag is true and counter is 1
。 - 但在某些运行中,可能会看到
WriterThread set flag to true
,但readerThread
进入 busy-wait 循环后永远不会退出。这是因为readerThread
可能无法看到flag
被设置为 true 的更新。
结论
如果不使用 volatile
关键字,flag
的写入更新对其他线程不可见,导致 readerThread
无法检测到 flag
的变化并一直在 busy-wait 循环中。这验证了不使用 volatile
关键字时可能出现的内存可见性问题。
通过多次运行这个程序,观察到 readerThread
不会始终成功读取到 flag
的变化,就可以确认不使用 volatile
关键字会导致多线程程序中的错误。这种错误在 volatile
关键字存在时不会发生,因为 volatile
能确保内存可见性和建立正确的 happens-before 关系。
这篇关于happens-before 关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!