happens-before 关系

2024-06-05 00:36
文章标签 关系 happens

本文主要是介绍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 关系:

  1. volatile 变量的写操作 happens-before 随后的读操作
    • 如果线程 A 对一个 volatile 变量进行写操作,然后线程 B 对这个 volatile 变量进行读操作,那么在 A 线程中对这volatile 变量的写操作 happens-before B 线程中的读操作。这意味着线程 B 将看到线程 A 写入的最新值。
  2. volatile 变量的写操作会禁止写之前的所有操作被重排序到写操作之后
    • 在对 volatile 变量进行写操作之前的所有操作,在内存模型上会被“刷回”主内存。即对 volatile 变量的写操作之前的所有普通变量的操作都将在写操作之前完成,并且在写操作之前的所有操作对后续的任何线程都是可见的。
  3. 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() 方法检测到 flagtrue,则它必然会看到 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();}}
}

代码解释

  1. writer() 方法
    • 设置 counter 为 1。
    • 设置 flag 为 true。
    • 打印当前线程名称及其操作。
  2. reader() 方法
    • 使用一个 busy-wait 循环等待 flag 变为 true。
    • flag 为 true 时,打印当前线程名称及其看到的 counter 值。
  3. main 方法
    • 创建并启动 writerThreadreaderThread
    • 使用 Thread.sleep(100) 确保 readerThread 先启动。

可能的结果

运行上述代码多次,可能会看到以下结果:

  • 有时,程序会如预期输出 WriterThread set flag to trueReaderThread 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 关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1031573

相关文章

POJ1269 判断2条直线的位置关系

题目大意:给两个点能够确定一条直线,题目给出两条直线(由4个点确定),要求判断出这两条直线的关系:平行,同线,相交。如果相交还要求出交点坐标。 解题思路: 先判断两条直线p1p2, q1q2是否共线, 如果不是,再判断 直线 是否平行, 如果还不是, 则两直线相交。  判断共线:  p1p2q1 共线 且 p1p2q2 共线 ,共线用叉乘为 0  来判断,  判断 平行:  p1p

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

读软件设计的要素04概念的关系

1. 概念的关系 1.1. 概念是独立的,彼此间无须相互依赖 1.1.1. 一个概念是应该独立地被理解、设计和实现的 1.1.2. 独立性是概念的简单性和可重用性的关键 1.2. 软件存在依赖性 1.2.1. 不是说一个概念需要依赖另一个概念才能正确运行 1.2.2. 只有当一个概念存在时,包含另一个概念才有意义 1.3. 概念依赖关系图简要概括了软件的概念和概念存在的理

数据依赖基础入门:函数依赖与数据库设计的关系

在数据库设计中,数据依赖 是一个重要的概念,它直接影响到数据库的结构和性能。函数依赖 作为数据依赖的一种,是规范化理论的基础,对数据库设计起着至关重要的作用。如果你是一名数据库设计的初学者,这篇文章将帮助你理解函数依赖及其在数据库设计中的应用。 什么是数据依赖? 数据依赖 是指同一关系中属性间的相互依赖和制约关系,它是数据库设计中语义的体现。在现实世界中,数据之间往往存在某种依赖关系,而这

c++ 和C语言的兼容性关系

C++ 和 C 语言有很高的兼容性,但也存在一些差异和限制。下面是它们的兼容性关系的详细介绍: 兼容性 C++ 是 C 的超集: C++ 语言设计为兼容 C 语言的语法和功能,大部分 C 代码可以在 C++ 编译器中编译运行。 标准库兼容性: C++ 标准库包含了 C 标准库的内容,如 stdio.h、stdlib.h、string.h 等头文件,但 C++ 的标准库也提供了额外的功能,如

七、Maven继承和聚合关系、及Maven的仓库及查找顺序

1.继承   2.聚合   3.Maven的仓库及查找顺序

file-max与ulimit的关系与差别

http://zhangxugg-163-com.iteye.com/blog/1108402 http://ilikedo.iteye.com/blog/1554822

【编程底层原理】方法区、永久代和元空间之间的关系

Java虚拟机(JVM)中的内存布局经历了几个版本的变更,其中方法区、永久代和元空间是这些变更中的关键概念。以下是它们之间的关系: 一、方法区: 1、方法区是JVM规范中定义的一个概念,它用于存储类信息、常量、静态变量、即时编译器编译后的代码等数据。 3、它是JVM运行时数据区的一部分,与堆内存一样,是所有线程共享的内存区域。 二、永久代(PermGen): 1、在Java SE 7之前,

笔记整理—内核!启动!—kernel部分(1)驱动与内核的关系

首先,恭喜完成了uboot部分的内容整理,其次补充一点,uboot第一部分和第二部分的工作不是一定的,在不同的版本中,可能这个初始化早一点,那个的又放在了第二部分,版本不同,造成的工作顺序不同,但终归是要完成基本内容初始化并传参给kernel的。         那么至于驱动与内核的关系,用一张图来说明最适合不过:         驱动位于OS层的中下层与硬件相接。驱动是内

一、关系模型和关系代数,《数据库系统概念》,原书第7版

文章目录 @[toc]一、引言1.1 什么是数据库1.2 数据完整性1.3 数据库的操作1.4 数据库的持久性1.5 数据库管理系统1.6 数据模型1.7 早期DBMS 二、关系模型2.1 什么是关系模型2.2 关系数据库的结构2.3 键2.4 约束2.5 数据操纵语言(DML)2.6 关系代数2.6.1 选择运算2.6.2 投影运算2.6.3 合并运算2.6.4 交运算2.6.5 差运算2.