【并发基础】Happens-Before模型详解

news2024/12/25 1:09:36

目录

一、Happens-Before模型简介

二、组成Happens-Before模型的八种规则

2.1 程序顺序规则(as-if-serial语义)

2.2 传递性规则

2.3 volatile变量规则

2.4 监视器锁规则

2.5 start规则

2.6 Join规则


一、Happens-Before模型简介

除了显示引用volatile关键字能够保证可见性以外,在Java中,还有很多的可见性保障的规则。

从JDK1.5开始,引入了一个happens-before的概念来阐述多个线程操作共享变量的可见性问题。所以我们可以认为在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须要存在 happens-before关系。这两个操作可以是同一个线程,也可以是不同的线程。

JMM可以通过happens-before关系向程序员提供跨线程的内存可见性,即如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作不在同一个线程中执行,但是JMM向程序员保证a操作对b操作可见。具体定义为:

  1. 如果一个操作happens-before另外一个操作(发生在另一个操作之前),那么第一个操作的执行结果对第二个操作可见,且第一个操作的执行顺序在第二个操作之前。
  2. 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。

上面两条规则,第一条,是JMM对程序员的保证。从程序员的角度可以这样理解happens-before:如果A happens-before B ,那么java内存模型将向程序员保证————A操作的结果一定对B操作可见,且A的执行顺序一定在B的前面。第二条是JMM对编译器和处理器重排序的约束原则,只要不改变程序的执行结果,编译器和处理器怎么优化都行。

二、组成Happens-Before模型的八种规则

happens-before模型是JMM要求多个操作必须满足的关系模型,而happens-before模型是由八条具体规则组成的,具体如下:

  • 程序顺序规则(as-if-serial语义):一个线程中的每个操作,happens-before于该线程的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对该volatile域的读。
  • 传递性规则:如果A happens-before B,且B happens-before C,那么A happens-before C。
  • start()规则: 如果线程A执行ThreadB.start(),那么A线程的ThreadB.start()操作happens-before于线程B的任意操作。
  • join()规则: 如果线程A执行ThreadB.join(),那么B线程中的任意操作happens-before于线程A从ThreadB.join()成功返回。
  • 程序中断规则:对线程interrupted()方法的调用happens-before与被中断线程的代码检测到中断时间的发生。
  • 对象finalize规则:一个对象初始化完成(构造函数执行结束)happens-before于发生它的finalize()方法的开始。

2.1 程序顺序规则(as-if-serial语义)

  • 不能改变程序的执行结果(在单线程环境下,执行的结果不变)
  • 依赖问题, 如果两个指令存在依赖关系,不允许重排序

这样听起来好像happens-before和as-if-serial语义没什么区别,但是一定要搞清楚as-if-serial是组成happens-before模型的其中一条规则,两者是包含的关系,下面对两者进行一个比较:

  1. as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before保证正确同步的多线程和单线程的执行结果不被改变。
  2. as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
  3. as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。
int a=0;
int b=0;
void test(){
    int a=1; a
    int b=1; b
    int c=a*b; c
}

a happens -before b ; b happens before c

2.2 传递性规则

a happens-before b 且 b happens- before c,那么 a happens-before c

2.3 volatile变量规则

volatile 修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作。该规则利用volatile关键字通过内存屏障机制来防止指令重排。

上图中的No表示不允许重排序,由上图我们就知道了使用volatile可以保证有序性。

public class VolatileExample {
    int a = 0;
    volatile boolean flag = false;
    public void writer() {
        a = 1;                           1
        flag = true; // 修改              2
    }
    public void reader() {
        if (flag) { // true              3
            // i的值最终一定会被设置为1
            int i = a;                   4
        }
    }
}

1 happens-before 2  -> 因为程序顺序规则和volatile规则。

3 happens-before 4  -> 因为程序顺序规则

2 happens-before 3  -> 因为volatile规则

1 happens-before 4  -> 因为传递性规则 

基于上面的规则,保证了最终i=1成立。

这里重点讲一下上面的1 happens-before 2 为什么成立。在上面给出了volatile重排序规则表的图片,其中有一条是代码中第一个操作是普通读写(在上面代码中对应了a=1),第二个操作是volatile写(在上面代码中对应了flag=true),这种情况是不允许重排序的,所以1 happens-before 2成立。

2.4 监视器锁规则

int x = 10;
synchronized(this) {
    // 后续线程读取到的x的值一定12
    if(x < 12) {
        x = 12;
    }
}
x = 12;

2.5 start规则

public class StartDemo{
    int x = 0;
    Thread t1 = new Thread(()->{
        // 读取x的值 一定是20
        if(x == 20){
        }
    });
    x = 20;
    // t1.start()以及之前的所有操作都发生在t1线程中任意操作之前
    t1.start();
}

2.6 Join规则

public class Test{
    int x=0;
    Thread t1=new Thread(()->{
        // t1线程中的任意操作都发生在当前线程中t1.join成功返回之前
        x = 200;
    });
    t1.start();
    t1.join(); //保证结果的可见性。
    //在此处读取到的x的值一定是200.
}

相关文章: 
【Java内存模型】Java内存模型(JMM)详解以及并发编程的三个重要特性(原子性,可见性,有序性)
【并发编程】volatile关键字最全详解,看这一篇就够了
【并发编程】synchronized关键字最全详解,看这一篇就够了
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/499966.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双目测距--5 双目相机 联合 YOLOv8

目录 效果&#xff1a; 1、立体矫正不改变图像尺寸 2、视差图尺寸与原图尺寸一致 3、视差图、深度信息图 4、几个重要的函数 createTracker() 5、代码 main.cpp utils.cpp 效果&#xff1a; 1、立体矫正不改变图像尺寸 左右相机图像立体矫正后&#xff0c;图像尺寸为变化…

freeRTOS中使用看门狗的一点思考

关于看门狗想必各位嵌入式软件开发的朋友应该都不会陌生的。在嵌入式软件开发中&#xff0c;看门狗常被用于监测cpu的程序是否正常在运行&#xff0c;如果cpu程序运行异常会由看门狗在达到设定的阈值时触发复位&#xff0c;从而让整个cpu复位重新开始运行。 看门狗的本质是一个…

Qt QQueue 安全的多线程队列、阻塞队列

文章目录 1. C queue 队列基本用法2. Qt QQueue 队列基本用法3. Qt QQueue 多线程队列4. Qt BlockingQueue 自定义线程安全的阻塞队列 1. C queue 队列基本用法 在C中&#xff0c;queue是一个模板类&#xff0c;用于实现队列数据结构&#xff0c;遵循先进先出的原则。 ♦ 常用…

测试3:用例

目录 1.测试用例的基本要素 2.测试用例的设计方法 1.基于需求的设计方法 2.等价类 1.概念 2.步骤: 3.例子 3.边界值 1.概念 2.步骤 3.例子 4.判定表 1.概念 2.设计测试用例 3.例子 5.正交排列 1.什么是正交表 2.测试用例 3.如何通过正交表设计测试用例 6.场景…

(3)Qt——信号槽

目录 1.信号槽的概念** 2.信号槽的连接*** 2.1自带信号 → 自带槽 2.2 自带信号 → 自定义槽 2.3 自定义信号 3. 参数传递** 3.1 全局变量 3.2 信号槽传参 4. 对应关系** 4.1 一对多 4.2 多对一 1.信号槽的概念** 信号槽指的是信号函数与槽函数的连接&#xff0c;可…

AI绘图入门 安装 stable-diffusion-webui

下面介绍了N卡&#xff0c;A卡&#xff0c;或CPU跑 stable-diffusion-webui的方法。 1.安装python 3.10.x https://www.python.org/downloads/ 2.安装Git https://git-scm.com/downloads 【非必要】打开代理工具&#xff08;比如clash&#xff09;然后在cmd配置git的http和…

软件测试相关概念

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 需求需求的定义测试人员眼中的需求为什么需求对测试人员如此重要如何深入理解需求 测试用例定义为什么要有测试用例 软件错误…

IT服务规划设计笔记

规划设计处于整个IT服务生命周期中的前端&#xff0c;其主要目的在于&#xff1a; &#xff08;1&#xff09;设计满足业务需求的IT服务 &#xff08;2&#xff09;设计SLA、测量方法和指标 &#xff08;3&#xff09;设计服务过程及其控制方法 &#xff08;4&#xff09;规…

learn_C_deep_9 (汇编角度理解return的含义、const 的各种应用场景)

return 关键字 不知道我们大家是否有一个疑惑&#xff1a;我们下载一个大型游戏软件&#xff08;王者荣耀&#xff09;&#xff0c;都要花几个小时去下载&#xff0c;但是一旦我们游戏连输&#xff0c;想要删除这个软件的时候&#xff0c;它仅仅只需要十几秒&#xff0c;这是为…

主题建模和文本聚类:理论与实践

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

真题详解(3FN)-软件设计(六十九)

真题详解&#xff08;构造二叉树&#xff09;-软件设计&#xff08;六十八)https://blog.csdn.net/ke1ying/article/details/130536155 学生信息学生id姓名性别1{家长ID}*班主任班级。 解析&#xff1a;当存在1对多的情况&#xff0c;要写个1{}*&#xff0c;中间用{}。 ()表…

一篇学会Gitlab搭建及使用

目录 一、Gitlab介绍 1、什么是Gitlab 二、搭建gitlab并实现ssl 1、配置yum源或下载gitlab包 2、安装依赖软件及获取GPG密钥 3、安装gitlab-ce 4、创建私有密钥 5、创建私有证书 6、创建CRT签名证书 7、利用openssl签署pem 证书 8、配置证书到gitlab 9、初始化gitla…

读书笔记:《图解CIO工作指南》

《图解CIO工作指南》第 4 版&#xff0c;日 . 野村综合研究所系统咨询事业本部 著&#xff0c;周自恒 译 大数据、云计算时代下的IT战略和IT实务 CIO工作&#xff1a;IT管理、IT架构、IT实践 以着眼企业未来的观点进行构思&#xff1a;可视化&#xff08;业务与系统&am…

初始化vue中data中的数据

当组件的根元素使用了v-if的时候, 并不会初始化data中的数据 如果想完全销毁该组件并且初始化数据,需要在使用该组件的本身添加v-if 或者是手动初始化该组件中的数据 初始化化数据的一些方法 Object.assign(this.$data, this.$options.data()) this.$data&#xff1a;当前的da…

TortoiseGit(大乌龟)安装教程(Git 图形化工具,告别手敲命令)

TortoiseGit安装教程 1. 下载TortoiseGit 官方下载地址&#xff1a;https://tortoisegit.org/download/ 自行选择下载对应版本&#xff08;大部分位64位&#xff09;&#xff0c;进行下载 2. 安装TortoiseGit 打开安装包&#xff0c;如下图所示&#xff1a; 点击 Next&…

IDM绿色最新2023中文版磁力下载工具

Internet Download Manager&#xff08;idm&#xff09;是一款优秀的多线程下载工具。它支持自动捕获剪贴板及浏览器及流媒体网站的音视频下载链接&#xff0c;还有批量队列下载、静默下载、站点抓取等众多功能选项&#xff0c;可以说是 Windows 平台上功能最为强大的多线程下载…

通过栈/队列/优先级队列/了解容器适配器,仿函数和反向迭代器

文章目录 一.stack二.queue三.deque&#xff08;双端队列&#xff09;四.优先级队列优先级队列中的仿函数手搓优先级队列 五.反向迭代器手搓反向迭代器 vector和list我们称为容器&#xff0c;而stack和queue却被称为容器适配器。 这和它们第二个模板参数有关系&#xff0c;可以…

录屏界鼻祖Camtasia 2023中文版功能介绍/下载安装激活教程

随着网络科技的迅速发展&#xff0c;所以对于电脑的使用率也就越来越高了&#xff01;然而&#xff0c;也可能跟这有关系&#xff0c;目前各种类型的软件层出不穷&#xff0c;当然也就包括了电脑录屏软件。这给我们造成了一些困难&#xff0c;究竟哪一款适合自己呢&#xff1f;…

DMA的补充笔记

DMA有两个总线&#xff1a; 1、DMA存储器总线&#xff1a;DMA通过该总线来执行存储器数据的传入和传出。 2、DMA外设总线&#xff1a;DMA通过该总线访问AHB外设&#xff08;AHB主要是针对高效率、高频宽以及快速系统模块所设计的&#xff0c;主要有Flash 存储器、复位和时钟控…

栈和队列OJ题思路分享之栈和队列互换(C语言实现)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:刷题分享⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你刷更多C语言和数据结构的题!   &#x1f51d;&#x1f51d; 栈和队列刷题分享二 1. 前言⚡…