21.Happens-Before原则

文章目录

  • Happens-Before原则
    • 1.Happens-Before规则介绍
    • 2.规格介绍
      • 2.1.顺序性规则(as-if-serial)
      • 2.2.volatile规则
      • 2.3.传递性规则
      • 2.4.监视锁规则
      • 2.5.start规则
      • 2.6.join()规则

Happens-Before原则

JVM内存屏障指令对Java开发工程师是透明的,是JMM对JVM实现的一种规范和要求。

JMM定义了一套自己的规则,Happens-Before规则(先行发生),并且确保两个Java语句必须存在Happens-Before关系,JMM尽量确保这俩个Java语句之间的内存可见性和指令的有序性。

1.Happens-Before规则介绍

  1. 程序顺序执行规则(as-if-serial规则)
    1. 在同一个线程中有依赖关系的操作规则按照先后顺序,前一个操作必须先行发生于后面一个操作(例如线程 A - 线程 B 有依赖关系,线程A必须先执行,然后才能执行线程B)。
    2. 总的来说就是单线程顺序无论怎么排序,对于结果来是不会变得。
  2. volatile变量规则
    1. 对于volatile修饰的变量,必须的写操作,必须先于发生对volatile的读操作
  3. 传递性规则
    1. 如果线程 A 先行发生于线程 B 操作,线程 B 操作又先行发生于 线程 C 操作,那么线程 A 必须先行发生于线程 C操作。
  4. 监视锁规则(Monitor Lock Rule)
    1. 对于一个监视锁的解锁操作先行发生 于 后续对这个监视锁的加锁操作。(同一个线程,解锁操作,必须先于加锁操作之前执行)
  5. start规则
    1. 对于线程的start操作先行发生于这个线程内部的其他任何操作。具体来说,如果线程A执行线程B的start()方法,那么线程A 启动线程 B 的 start()方法,先于发生线程B中的任意操作
  6. join规则
    1. 如果线程A执行了 线程B的join()方法,那么线程B的任意操作,先行发生于线程A所执行的 线程B的join()方法

2.规格介绍

2.1.顺序性规则(as-if-serial)

顺序性规则的具体内容:一个线程内,按照代码顺序书写在前面的操作,先行发生于 书写在后面的操作。

顺序性规则是Java内存模型(JMM)中一个基本的概念,它保证了在一个线程内部观察到的操作执行顺序,会符合程序代码的逻辑顺序。这意味着,在单线程环境下,程序的执行将保持我们书写的指令顺序,不会出现乱序执行的情况。简单来说,如果在代码中先写了操作A,然后是操作B,那么在同一个线程中执行时,A必然会在B之前完成,不会出现B先于A执行的结果。

虽然可能发生重排序,但是他只对不存在数据依赖代码行,进行指令重排序

int x = 0;
int y = 0;void method() {x = 1; // 操作Ay = 2; // 操作B
}

根据顺序性规则,在method方法内部,操作A(x = 1;)总是会先于操作B(y = 2;)执行完成。这意味着,在同一个线程调用method方法后,任何检查xy值的后续代码都将看到x为1且y为2,不可能看到y已经更新为2而x还未被设置为1的情况。

2.2.volatile规则

volatile的具体内容:对一个volatile变量的的写,先行发生于任意后续对这个volatile变量的读。

volatile的主要作用:

  1. **保证可见性:**对一个volatile变量的修改,能够立即刷新到主内存中,所有其他线程对该变量的访问都会重新从主内存中获取最新值,从而确保了变量的可见性。
  2. **禁止指令重排序:**volatile除了保证变量的可见性外,还有一层重要的意义在于它能禁止指令的重排序优化。具体来说,对volatile变量的写操作,在写后,会有一个内存屏障(Memory Barrier),确保该写操作不会被重排序到之后的读写操作之前;相应的,对volatile变量的读操作前也会有一个内存屏障,确保该读操作不会被重排序到之前的写操作之后。这正是你提到的“对一个volatile变量的写,先行发生于任意后续对这个volatile变量的读。
package com.hrfan.thread;import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;// 每个参与测试的线程都将拥有其独立的实例@State(Scope.Thread)
public class VolatileRecorderDemo {int x = 10;int value = 0;boolean flag = false;public  void update() {x = 100;      // 代码1flag = true;  // 代码2}public  synchronized void add() {if (flag) {  // 代码3value = x + x;System.out.println(Thread.currentThread().getName() + "- value:" + value);}}@Benchmarkpublic void test(){// 假设线程1 执行 update(),线程2 执行 add(),代码1 和 代码2 并没有依赖关系,所以 代码1 和 代码2 可能被重排序,他们排序后结果为// flag = true;// value = 100;// 假设重排序后,线程1 执行了 flag = true; 此时还没有执行  value = 10; 线程2 开始执行 add()方法.此时value的值为 10// 那么最终结果 value = 20 而不是 200VolatileRecorderDemo volatileRecorderDemo = new VolatileRecorderDemo();volatileRecorderDemo.update();volatileRecorderDemo.add();}@Test@DisplayName("测试")public void start() {Options opt = new OptionsBuilder().include(VolatileRecorderDemo.class.getSimpleName())// 预热3轮.warmupIterations(3)// 度量5轮.measurementIterations(5)// 设置线程数,比如设置为4个线程.threads(200)// fork的JVM实例数量 每轮任务数量.forks(5).build();try {new Runner(opt).run();} catch (RunnerException e) {throw new RuntimeException(e);}}
}

假设线程A执行 update()方法,线程B 执行 add()方法,因为代码1 和代码2并没有依赖关系,所以代码1 和代码2就可能会被重排序,他们重排序后的次序可能为

flag = true;  // 代码2
value = 100;      // 代码1

线程A执行重排代码后,在完成 代码2 之前(flag = true),假设线程B开始执行 add()方法,将 x 的值进行累加,此时的 x 的值就是 10 而不是100,那么 x 累加完成后的值就是 20。这个不是我们想要的结果,为了获取正确的结果,我们必须阻止代码进行重排序,为以上代码的flag成员属性增加 volatile修饰,

public class VolatileRecorderDemo {int x = 10; int value = 0;volatile boolean flag = false;public  void update() {x = 100;      // 代码1flag = true;  // 代码2}public  synchronized void add() {if (flag) {  // 代码3value = x + x; // 代码4System.out.println(Thread.currentThread().getName() + "- value:" + value);}}
}

从前面的顺序性规则,已经知道,如果 代码2的操作为 volatile写,无论第一个操作是什么都不能重排序。所以代码1 不会排到 代码2 后面的。

代码3 为读取 flag(volatile)变量,那么 代码4 就不会被重排序到 代码3 之前。

2.3.传递性规则

如果线程 A 先行发生于线程 B 操作,线程 B 操作又先行发生于 线程 C 操作,那么线程 A 必须先行发生于线程 C操作。

例如,如果线程A修改了一个变量,然后线程B读取了这个变量的值,并且线程B接着修改了另一个变量,线程C随后读取线程B修改的变量值,根据传递性规则,线程A对第一个变量的修改操作在逻辑上必须在C线程读取第二个变量值之前发生,保证了跨线程间操作的正确序列化。

2.4.监视锁规则

对于一个监视锁的解锁操作先行发生 于 后续对这个监视锁的加锁操作。(同一个线程,解锁操作,必须先于加锁操作之前执行)

在Java内存模型中,监视锁规则规定了对于同一个监视器(Monitor,通常指由synchronized关键字实现的同步块或方法)的解锁操作,必须先行发生于后续针对该监视器的加锁操作。这意味着,如果线程A解锁了一个监视器(即退出了同步代码块或方法),那么这个解锁操作将发生在任何其他线程B(包括线程A自身)随后成功获取这个监视器锁的操作之前。这一规则确保了以下几点:

  • 线程间的操作顺序性:锁的解锁和加锁操作为线程间的操作提供了一种全局的顺序关系,帮助维护操作的执行顺序性,这对于理解并发程序的行为至关重要。
  • 内存可见性:解锁操作之前的所有内存写操作对随后的加锁操作后的线程都是可见的,确保了数据的正确同步。
  • 互斥性:保证了在任何时刻只有一个线程可以持有监视器的锁,从而防止数据竞争条件和不一致状态的读取。
public class VolatileRecorderDemo2 {int x = 10;int value = 0;boolean flag = false;public synchronized void update() {value = 100;  // 代码1flag = true;  // 代码2}public synchronized void add() {if (flag) {  // 代码3value = x + x; // 代码4System.out.println("x = " + x);}}}

先获取锁的线程,读 value 赋值之后,释放锁,那么另外一个线程 再去获取锁的时候,一定能看到对 value赋值的改动。

在这里插入图片描述

2.5.start规则

对于线程的start操作先行发生于这个线程内部的其他任何操作。具体来说,如果线程A执行线程B的start()方法,那么线程A 启动线程 B 的 start()方法,先于发生线程B中的任意操作

简单来说,就是 主线程A 启动 子线程B 后,线程B 能看到 线程A启动操作前的任何操作

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class StartRuleDemo {private static final Logger log = LoggerFactory.getLogger(StartRuleDemo.class);private int x = 0;private int y = 0;private Boolean flag = false;@Test@DisplayName("测试start()规则")public void testStartRule() throws InterruptedException {// 创建线程B 先不启动Thread threadB = new Thread(this::printInfo, "线程B");// 线程A 先 对数据进行赋值操作Thread threadA = new Thread(() -> {x = 100;y = 200;flag = true;// 启动线程BthreadB.start();}, "线程A");threadA.start();}public void printInfo() {log.error("============================ 线程B打印相关信息 ============================");log.error("x = {}", x);log.error("y = {}", y);log.error("flag = {}", flag);}
}

在这里插入图片描述

2.6.join()规则

join()规则的具体内容是:如果线程A 执行 threadB.join() 操作后,并成功返回。那么线程B中的任意操作先行发生于线程A的 threadB.Join()

这意味着,通过调用join(),线程A确保了线程B的所有操作都已经完成了,这包括线程B的执行、修改共享变量、资源释放等,所有这些都完全发生在线程A得以继续其后续代码执行之前。这保证了线程间操作的顺序性和数据的一致性,避免了因并发执行可能产生的数据竞争问题。

join() 规则 刚好和 start()规则 相反

在Java中,Thread.join()方法是一个非常重要的同步机制,它允许一个线程等待另一个线程执行完成后再继续执行。

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class StartRuleDemo {private static final Logger log = LoggerFactory.getLogger(StartRuleDemo.class);private int x = 0;private int y = 0;private Boolean flag = false;@Test@DisplayName("测试join()规则")public void testJoinRule() throws InterruptedException {// 创建线程B 先不启动Thread threadB = new Thread(this::updateThreadB, "线程B");// 线程A 先 对数据进行赋值操作Thread threadA = new Thread(() -> {// 启动线程BthreadB.start();try {threadB.join();} catch (InterruptedException e) {throw new RuntimeException(e);}printInfo();}, "线程A");threadA.start();}public void printInfo() {log.error("============================ 线程A打印相关信息 ============================");log.error("x = {}", x);log.error("y = {}", y);log.error("flag = {}", flag);}public void updateThreadB(){this.x = 100;this.y = 200;this.flag = true;}
}

在这里插入图片描述

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

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

相关文章

SpringBoot使用rsa-encrypt-body-spring-boot实现接口加解密

废话不多说&#xff0c;直接上代码 引入依赖 <dependency><groupId>cn.shuibo</groupId><artifactId>rsa-encrypt-body-spring-boot</artifactId><version>1.0.1.RELEASE</version> </dependency>配置文件 rsa:encrypt:# 是…

Leetcode 剑指 Offer II 079.子集

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返…

系统升级中,请稍后...Time: 2024-05-26 10:59:46 Code:OE.20001

没错&#xff01;你能看到“系统升级中&#xff0c;请稍后…Time: 2024-05-26 10:59:46 Code:OE.20001”这个提示你找到这里就对了&#xff01;以上提示是婚恋交由系统奥壹的oelove的报错内容&#xff01;为什么报错&#xff1f;那说明你不是商业用户&#xff0c;默认程序是需要…

【spring】@PathVariable注解学习

PathVariable介绍 PathVariable是Spring框架中的一个注解&#xff0c;主要用于处理RESTful风格URL中的路径变量。在RESTful接口设计中&#xff0c;我们经常将资源的ID或者其他标识信息直接放在URL路径中&#xff0c;而不是作为查询参数。PathVariable注解使得控制器方法能够轻…

FreeRTOS_同步互斥与通信_队列集_学习笔记

FreeRTOS_同步互斥与通信_环形buffer、队列_学习笔记 5.5 队列集 要支持多个输入设备时&#xff0c;我们需要实现一个“InputTask”&#xff0c;它读取各个设备的队列&#xff0c;得到数据后再分别转换为游戏的控制键。 InputTask如何及时读取到多个队列的数据&#xff1f;要…

C#的奇技淫巧:利用WinRM来远程操控其他服务器上的进程

前言&#xff1a;有时候远程服务器的进程你想偷偷去围观一下有哪些&#xff0c;或者对一些比较调皮的进程进行封杀&#xff0c;或者对一些自己研发的服务进行远程手动启动或者重启等&#xff0c;又不想打开远程桌面&#xff0c;只想悄咪咪地执行&#xff0c;那也许下面的文章会…

如何使用pycrypt加密工具测试反病毒产品的检测性能

关于pycrypt pycrypt是一款基于Python 3语言开发的加密工具&#xff0c;广大研究人员可以使用该工具来尝试绕过任意类型的反病毒产品&#xff0c;以检测目标反病毒产品的安全性能。 功能介绍 1、目前已知反病毒产品检测率为0/40&#xff1b; 2、支持绕过任意EDR解决方案&#…

中间件-------RabbitMQ

同步和异步 异步调用 MQ MQ优势&#xff1a;①服务解耦 ②异步调用 ③流量削峰 结构 消息模型 RabbitMQ入门案例&#xff0c;实现消息发送和消息接收 生产者&#xff1a; public class PublisherTest {Testpublic void testSendMessage() throws IOException, TimeoutExce…

Java进阶学习笔记21——泛型概念、泛型类、泛型接口

泛型&#xff1a; 定义类、接口、方法的时候&#xff0c;同时声明了一个或者多个类型变量&#xff08;如: <E>&#xff09;,称之为泛型类、泛型接口、泛型方法&#xff0c;我们统称之为泛型。 说明这是一个泛型类。 如果不使用泛型&#xff0c;我们可以往ArrayList中传…

PyQt6--Python桌面开发(34.QStatusBar状态栏控件)

QStatusBar状态栏控件 self.statusBar.showMessage(q.text()菜单选项被点击了,5000)

平安养老险陕西分公司:举办“贺司庆·员工橙心面对面”活动

为践行新价值文化与“三省”推广&#xff0c;平安养老险陕西分公司以集团36周年司庆为契机结合“员工聆听计划”指引要求&#xff0c;举办“贺司庆&#xff0c;员工橙心面对面”活动。 活动邀请西北大学公共管理学院高阳教授为分公司员工带来生动有趣的《压力管理新科学》心理课…

MyBatis-Plus 从入门到精通

MyBatis-Plus 从入门到精通 前言快速入门创建一个SpringBoot项目导入依赖配置数据库创建一个实体类创建一个mapper接口在SpringBoot启动类上配置mapper接口的扫描路径在数据库中创建表编写一个SpringBoot测试类 核心功能注解CRUD接口Mapper CRUD接口Service CRUD 接口条件构造器…

安卓开发--安卓使用Echatrs绘制折线图

安卓开发--安卓使用Echatrs绘制折线图 前期资料安卓使用Echarts绘制折线图1.1 下载 Echarts 安卓资源1.2 新建assets文件1.3 新建布局文件1.4 在布局文件中布局WebView1.5 在活动文件中调用 最终效果 前期资料 Echarts 官网样式预览: https://echarts.apache.org/examples/zh/…

phonenumbers,一个强大的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个强大的 Python 库 - phonenumbers。 Github地址&#xff1a;https://github.com/daviddrysdale/python-phonenumbers 在现代应用程序中&#xff0c;处理和验证电话号码是一项常见的需求。无论…

《欢乐钓鱼大师》辅助:新手钓鱼全新攻略大全!

《欢乐钓鱼大师》是一款充满趣味和挑战的钓鱼游戏。在游戏中&#xff0c;玩家不仅可以体验钓鱼的乐趣&#xff0c;还可以通过不同的钓鱼竿和鱼卡来提升自己的钓鱼技能。为了帮助新手和老玩家更好地体验游戏&#xff0c;本文将为您提供详细的游戏攻略。 1. 游戏目标 在《欢乐钓…

2024年云南特岗教师报名流程,超详细,明天就开始报名哦!

2024年云南特岗教师报名流程&#xff0c;超详细&#xff0c;明天就开始报名哦&#xff01;

【Nginx <三>⭐️⭐️⭐️】Nginx 负载均衡使用

目录 &#x1f44b;前言 &#x1f440;一、 负载均衡概述 &#x1f331;二、项目模拟 2.1 环境准备 2.2 启动多个服务器 2.3 配置 Nginx 2.4 测试配置 &#x1f49e;️三、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;前不久开始学习了 Nginx 的使用&#xff0c;在…

github加速访问及资源一秒代理下载

如果你想加速打开github网页&#xff0c;可以采用以下方法&#xff0c;仅需一个插件。 1.代理加速访问 打开gitee网站&#xff0c;搜索dev-sidecar关键字&#xff0c;然后找到星星最多的项目 可以阅读项目说明&#xff0c;找到感兴趣的内容或是直接下载DevSidecar桌面应用程序…

SAP-技巧篇实现GUI免密码登录

做为上千万的软件怎么会没有免密码登录呢 01 — 背景需求 如何实现SAP GUI免密码登录&#xff0c;不输入密码实现系统自动登录。 免责声明&#xff1a;谨慎设置&#xff0c;因免密登录导致数据泄密&#xff0c;作者概不负责。 02 — 实现 客户端要求&#xff1a;SAP G…

2024年5月LLM最新排名:GPT-4o出道即巅峰!国内3个大模型榜上有名!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…