当前位置: 首页 > news >正文

as-if-serial与happens-before原则详解

文章目录

  • 前言
  • 详解
    • 解决多线程下的问题
  • Happens-before原则
  • 总结
    • as-if-serial语义
    • happens-before的例子

在这里插入图片描述

前言

"as-if-serial"原则是Java内存模型中的一个重要概念。该规则规定:不管怎么重排序(编译期间的重排序,指令级并行的重排序,内存系统的重排序等),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

为了获取更好的性能,编译器和处理器常常会对指令做重排序,但是他们必须遵守数据依赖性,即在不改变单线程程序执行结果的前提下进行指令重排序。例如,对于以下代码:

int a = 1; //语句1
int b = 2; //语句2
int c = a + b; //语句3

语句1和语句2没有数据依赖性,可以重排序。但是语句3依赖于语句1和语句2,所以它不能被重新排序到语句1或语句2之前。

然而,这个原则只适用于单线程,对于多线程,就需要遵守happens-before原则,确保线程间操作的有序性和可见性。

详解

as-if-serial原则是说,不考虑并发编程的情况,Java程序的执行结果应该与该程序在串行化环境中的执行结果一致。简单来说,就是程序在执行过程中无论如何重新排序(例如,编译器的优化,处理器的优化),只要最终呈现出的执行结果与串行执行的结果一致,那么这样的重排序是被允许的。

例如,考虑以下代码:

int a = 1;
int b = 2;
int c = a + b;

依照as-if-serial原则,虽然在执行过程中,可能会将int b = 2;语句移到int c = a + b;之后执行,但是最终的执行结果(c的值)仍然与串行化执行的结果一致。

但在并发环境下,as-if-serial原则可能会导致问题。例如:

public class Counter {private int count = 0;public void increment() {count++;}public int getCount() {return count;}
}
在并发环境下,如果有两个线程同时执行 increment() 方法,由于 as-if-serial 原则,编译器或处理器可能将 count++ 重排序为两个操作:先读取 count 的值,然后再写回 count+1 的值。在两个线程并发执行的情况下,可能第一个线程读取了 count 的值,然后第二个线程也读取了 count 的值,然后两个线程都将 count+1 的值写回,导致 count 的值只增加了 1,而不是预期的 2。下面是一个更具体的例子来说明 as-if-serial 原则可能导致的问题:```java
public class Example {private int a = 0;private int flag = 0;public void writer() {a = 1;          //1flag = 1;       //2}public void reader() {if (flag == 1)  //3{int i = a;  //4}}
}

在这个例子中,假设有两个线程,一个线程执行 writer() 方法,另一个线程执行 reader() 方法。按照 as-if-serial 原则,编译器或处理器可能会将 writer() 方法中的两行代码的顺序交换,即先执行 flag = 1,然后再执行 a = 1。如果这样的重排序发生,reader() 方法可能会在 a 被赋值之前就读取到 flag 的值为 1,然后读取到 a 的值为 0,而不是预期的 1。

解决多线程下的问题

Java通过使用volatile关键字来解决这个问题。

当一个变量被volatile修饰后,它将具备两种特性:

  1. 可见性(Visibility): 当一个线程修改了一个volatile变量的值,新值对于其他线程来说是可以立即得知的。

  2. 禁止指令重排序优化:普通的变量仅仅会满足1,而被volatile修饰过的变量由于禁止指令重排序优化,可以满足2。

这两种特性使得volatile变量在并发编程中非常有用。

例如,在上面的例子中,如果flag变量被声明为volatile,那么两个线程看到的flag永远都是最新的,如果writer线程更改了flag的值,reader线程立刻就能看到,这就解决了可见性问题。同时,对一个volatile变量的任何写操作,都会立即刷新到主存,因此在写操作后的任何读操作,都会看到这个新值。

volatile关键字还有一个额外的特性就是禁止指令重排序。编译器在执行优化时,可能会重新排序代码的执行顺序。当flag变量被volatile关键字修饰后,编译器就不会对这个变量前后的代码进行重排序,这就保证了顺序性,解决了重排序问题。

Happens-before原则

是用来判断数据是否存在竞争、线程之间的修改操作是否对其他线程可见的原则。

引入happens-before原则的原因主要有两个:

  1. 解决可见性问题:在并发编程中,由于线程切换、编译器优化等原因,一个线程对共享变量的修改不一定立即对其他线程可见,这就导致了可见性问题。通过Happens-before原则,我们可以清楚地知道哪些操作对其他线程可见。

  2. 解决有序性问题:在并发编程中,由于指令重排序,代码的执行顺序可能会与我们编写的顺序不同。通过Happens-before原则,我们可以明确的知道操作的前后顺序。

Happens-before原则包括以下几种规则:

  1. 程序次序规则(Program order rule):一个线程中的每个操作,happens-before于该线程中的任意后续操作。

  2. 监视器锁规则(Monitor lock rule):对一个锁的解锁,happens-before于随后对这个锁的加锁。

  3. volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volitile域的读。

  4. 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

  5. start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作 happens-before于线程B中的任意操作。

  6. join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

例如,假设有两个线程A和B,线程A写入一个volatile变量,然后线程B读取这个变量。那么线程A的写入操作happens-before线程B的读取操作,线程B可以看到线程A的写入。

再例如,假设一个线程先解锁一个对象,然后另一个线程锁定这个对象。那么第一个线程的解锁操作happens-before第二个线程的加锁操作,第二个线程可以看到第一个线程解锁前的所有操作。

总结

as-if-serial语义

这个程序中,尽管编译器或处理器可能会重排序代码,但是从程序的行为上看,它就如同按照源代码的顺序串行执行的,这就是as-if-serial语义。

public class AsIfSerial {private static int x = 0;private static int y = 0;public static void main(String[] args) {x = 1;y = 2;int a = x;int b = y;System.out.println("a = " + a + ", b = " + b);}
}

happens-before的例子

在这个程序中,线程A的flag = true操作happens-before线程B的if(flag)操作,因为它们之间有volatile变量规则和join规则。所以,线程B可以看到线程A将flag设置为true。

public class HappensBefore {private static volatile boolean flag = false;public static void main(String[] args) throws InterruptedException {Thread threadA = new Thread(() -> {flag = true;});Thread threadB = new Thread(() -> {if (flag) {System.out.println("ThreadB sees flag = true");}});threadA.start();threadA.join();threadB.start();threadB.join();}
}

as-if-serial是一种程序优化原则,它允许编译器和处理器对程序进行各种优化,包括重新排序指令等,但优化后的程序必须与按照程序源代码顺序执行的结果一致。这样可以保证程序的正确性,又可以提高程序的运行效率。

happens-before则是一种描述多线程程序中两个或多个操作之间可能存在的偏序关系。如果操作A happens-before操作B,那么A的结果对B是可见的,即B可以看到A的效果。happens-before关系可以保证多线程程序的正确同步。

相关文章:

as-if-serial与happens-before原则详解

文章目录 前言详解解决多线程下的问题 Happens-before原则总结as-if-serial语义happens-before的例子 前言 "as-if-serial"原则是Java内存模型中的一个重要概念。该规则规定:不管怎么重排序(编译期间的重排序,指令级并行的重排序&…...

基于Yolov8的工业小目标缺陷检测(2):动态蛇形卷积(Dynamic Snake Convolution),实现暴力涨点 | ICCV2023

目录 1.工业油污数据集介绍 1.1 小目标定义 1.2 难点 1.3 工业缺陷检测算法介绍 1.3.1 YOLOv8...

ARM64汇编基础

ARM64汇编基础 主要内容 到目前为止,大部分的移动设备都是64位的arm架构,一直想抽个时间系统学习下,这个周末就专门来学习下。毕竟两天的时间,也只是简单的入门了解下,为后续工作和学习打下基础。 本次学习的主要内容…...

Nodejs 第十六章(ffmpeg)

FFmpeg 是一个开源的跨平台多媒体处理工具,可以用于处理音频、视频和多媒体流。它提供了一组强大的命令行工具和库,可以进行视频转码、视频剪辑、音频提取、音视频合并、流媒体传输等操作。 FFmpeg 的主要功能和特性: 格式转换:…...

k8s集群部署es

集群内创建需要用到存储,此处举例使用腾讯云cfs共享存储 内存limits限制不需要加,否则会经常内存溢出导致es集群故障 apiVersion: apps/v1 kind: StatefulSet metadata:name: es7-clusternamespace: elasticsearch spec:serviceName: es-clusterreplica…...

学习记忆——宫殿篇——记忆宫殿——记忆桩——火车+外院+客厅+卧室

护板 警示灯 烟筒 采集箱 司炉室 桥 电线杆 棚顶 车厢 护栏 植物 石阶 水泥台 竹门 树干 躺椅 柱子 墙 池 洞 方灯 枕头 树 浴池 墙 射灯 藤条 浴巾框 耳环 窗户 灯 沙发 壁炉 吊灯 兵马俑 门 石佛 沙发椅 圆木 弧形木箱盖 床 窗帘 画板 纸伞 花 沙发背 颜料 抽屉...

QT用户登录注册,数据库实现

登录窗口头文件 #ifndef LOGINUI_H #define LOGINUI_H#include <QWidget> #include <QLineEdit> #include <QPushButton> #include <QLabel> #include <QMessageBox>#include <QSqlDatabase> //数据库管理类 #include <QSqlQuery> …...

GEE学习总结(9)——像元二分法计算月度植被覆盖度(MODIS)

像元二分法计算植被覆盖度 通过MODIS的NDVI数据集MOD13Q1和像元二分法计算植被覆盖度 var multi_NDVI ee.ImageCollection(MODIS/006/MOD13Q1).filterDate(2015-06-01, 2016-09-01).select(NDVI).max().divide(10000).clip(geometry);var ndviVis {min: 0.0,max: 1,palette…...

RabbitMQ用户命令_策略_日志

RabbitMQ相关安装 Centos离线安装RabbitMQ并开启MQTT Docker安装rabbitMQ RabbitMQ集群搭建和测试总结_亲测 Docker安装RabbitMQ集群_亲测成功 RabbitMQ创建管理员命令 #查看当前用户命令&#xff1a; rabbitmqctl list_users#创建用户和密码 rabbitmqctl add_user admin…...

停车场系统、智慧城市停车、智慧社区、物业管理、新能源充电、人脸门禁 uniapp 系统源码

1. 智慧停车 支持模式 封闭性单个停车场路边停车(车位级管理)大小场(场中场)&#xff0c;多场子并行或嵌套 所有者模式 统一平台管理总平台下子账号(区域代理)自建场地资源&#xff0c;自行维护数据总平台下子账号(区域代理)再分配和单个停车场管理人员(物业管理/维保/保安/财务…...

Linux磁盘管理

物理设备的命名规则 在linux系统中一切都是文件&#xff0c;硬件设备也不例外。即然是文件&#xff0c;就必须有文件名称。系统内核中的udev设备管理器会自动把硬件名称规范起来&#xff0c;目的是让用户通过设备文件的名字可以看出设备大致的属性以及分区信息等&#xff1b;在…...

vue学习之vue cli创建项目

安装 node.js https://nodejs.org/en 安装 vue cli npm install -g @vue/cli --registry=https://registry.npm.taobao.org创建项目 执行创建命令,回车vue create vue-cli-learning选择 “Manually select features”,回车 “空格” 关闭 Linter / Formatter 选项,回车...

K8S:Pod容器中的存储方式及PV、PVC

文章目录 Pod容器中的存储方式一&#xff0e;emptyDir存储卷1.emptyDir存储卷概念2.emptyDir存储卷示例 二.hostPath存储卷1.hostPath存储卷概念2.hostPath存储卷示例 三.nfs共享存储卷1.nfs共享存储卷示例 四.PV和PVC1.PV、PVC概念2.PVC 的使用逻辑及数据流向3.storageclass插…...

uni-app跳转到另一个app

第一步&#xff1a; 首先要知道 app的包名 获取方式如下 第二步&#xff1a; 在第一个 demo1 app 一个页面中需要一个按钮去跳转 方法如下 <template><view class"content"><button click"tz">跳转</button></view> </…...

如何通过一键导出导入数据实现批量重命名文件名称

在日常办公中&#xff0c;我们经常需要对大量的文件进行重命名&#xff0c;以便更好地管理和查找文件。而且&#xff0c;有时候我们还需要将文件名称翻译成其他语言&#xff0c;以适应不同的工作需求。如何高效地完成这项任务呢&#xff1f;接下来&#xff0c;我将介绍一种方法…...

CTF —— 网络安全大赛(这不比王者好玩吗?)

前言 随着大数据、人工智能的发展&#xff0c;人们步入了新的时代&#xff0c;逐渐走上科技的巅峰。 \ ⚔科技是一把双刃剑&#xff0c;网络安全不容忽视&#xff0c;人们的隐私在大数据面前暴露无遗&#xff0c;账户被盗、资金损失、网络诈骗、隐私泄露&#xff0c;种种迹象…...

3D模型转换工具HOOPS Exchange如何实现OBJ格式轻量化?

什么是OBJ模型轻量化&#xff1f; OBJ格式是一种常用的三维模型文件格式&#xff0c;通常包含模型的顶点、法线、纹理坐标等信息&#xff0c;但有时候这些信息可能会使模型文件变得较大&#xff0c;不利于网络传输、加载和运行。 OBJ&#xff08;Object&#xff09;模型轻量化…...

命令模式-

定义&#xff1a;又叫动作模式或事务模式。指的是将一个请求封装成一个对象&#xff0c;使发出请求的责任和执行请求的责任分割开&#xff0c;然后可以使用不同的请求把客户端参数化&#xff0c;这样可以使得两者之间通过命令对象进行沟通&#xff0c;从而方便将命令对象进行储…...

进程的管理

#include <unistd.h> void _exit(int status); #include <stdlib.h> void _Exit(int status); status参数&#xff1a;是进程退出时的状态信息&#xff0c;父进程在回收子进程资源的时候可以获取到 #include<stdio.h> #include<stdlib.h> #includ…...

绿色科技:可持续发展的创新解决方案

标题绿色科技&#xff1a;可持续发展的创新解决方案 摘要引言绿色能源创新1. 太阳能和风能2. 储能技术 可再生资源管理3. 智能农业4. 循环经济 智能城市的未来5. 智能交通6. 城市感知 可持续生活方式7. 可持续建筑8. 智能家居 总结参考资料 博主 默语带您 Go to New World. ✍ …...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

c++第七天 继承与派生2

这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分&#xff1a;派生类构造函数与析构函数 当创建一个派生类对象时&#xff0c;基类成员是如何初始化的&#xff1f; 1.当派生类对象创建的时候&#xff0c;基类成员的初始化顺序 …...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!

本文介绍了一种名为AnomalyAny的创新框架&#xff0c;该方法利用Stable Diffusion的强大生成能力&#xff0c;仅需单个正常样本和文本描述&#xff0c;即可生成逼真且多样化的异常样本&#xff0c;有效解决了视觉异常检测中异常样本稀缺的难题&#xff0c;为工业质检、医疗影像…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...