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

news2024/11/25 7:37:41

文章目录

  • 前言
  • 详解
    • 解决多线程下的问题
  • 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;          //1
        flag = 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关系可以保证多线程程序的正确同步。

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

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

相关文章

MYSQL存储引擎基础知识介绍

下面重点介绍几种常用的存储引擎,并对比各个存储引擎之间的区别,以帮助读者理解 不同存储引擎的使用方式。 MyISAM MyISAM是 MySQL的默认存储引擎。MyISAM不支持事务、也不支持外键,其优势是访 问的速度快,对事务完整性没有要求或者以 SEL…

怒刷LeetCode的第11天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一:迭代 方法二:递归 方法三:指针转向 第二题 题目来源 题目内容 解决方法 方法一:快慢指针 方法二:Arrays类的sort方法 方法三:计数器 方法四…

【Java 基础篇】Java线程组详解

Java线程组是一种用于管理线程的机制,它允许你将线程组化为一个单元,并对组内的线程进行一些操作和控制。本文将详细介绍Java线程组的概念、如何创建和管理线程组,以及线程组的一些常见用法。 什么是线程组? 线程组是一个用于组…

python生成PDF报告

前言 最近接到了一个需求-将项目下的样本信息汇总并以PDF的形式展示出来,第一次接到这种PDF的操作的功能,还是有点慌的,还好找到了reportlab这个包,可以定制化向PDF写内容! 让我们由简入深进行讲解 一、reportlab是…

2023年腾讯云轻量服务器测评:16核 32G 28M 配置CPU测试

腾讯云轻量应用服务器16核32G28M配置优惠价3468元15个月(支持免费续3个月/送同配置3个月),轻量应用服务器具有100%CPU性能,系统盘为380GB SSD盘,28M带宽下载速度3584KB/秒,月流量6000GB,折合每天…

由于找不到d3dx9_43.dll,无法继续执行代码要怎么解决

D3DX9_43.dll是一个动态链接库文件,它是DirectX的一个组件,主要用于支持一些旧版本的游戏和软件。当电脑缺少这个文件时,可能会导致这些游戏和软件无法正常运行。例如,一些老游戏可能需要D3DX9_43.dll来支持图形渲染等功能。此外&…

二分类问题的解决利器:逻辑回归算法详解(一)

文章目录 🍋引言🍋逻辑回归的原理🍋逻辑回归的应用场景🍋逻辑回归的实现 🍋引言 逻辑回归是机器学习领域中一种重要的分类算法,它常用于解决二分类问题。无论是垃圾邮件过滤、疾病诊断还是客户流失预测&…

git安装配置教程

目录 git安装配置1. 安装git2. git 配置3.生成ssh key:4. 获取生产的密钥3. gitee或者github添加ssh-key4.git使用5. git 使用-本地仓库与远程仓库建立连接第一步:进入项目文件夹,初始化本地仓库第二步:建立远程仓库。 建立远程连接的小技巧 …

strcpy常见的错误

char* arr "handsome"; strcpy(arr, "pretty"); printf("%s\n", arr); 这个程序在编译器上是运行不出来的,因为arr是一个字符串指针,它的值不可以被修改,可以将arr改成字符数组

录屏没有声音怎么办,3个方法教你解决

随着科技的不断发展,人们越来越依赖电子设备进行工作和学习。在这个过程中,录屏已经成为了一种必要的技能。无论是手机还是电脑,我们都可以通过录屏来记录重要的信息。但是,有时候我们在录屏时会发现声音无法正常录制,…

前后端分离的低代码快速开发框架

低代码开发正逐渐成为企业创新的关键工具。通过提高开发效率、降低成本、增强灵活性以及满足不同用户需求,低代码开发使企业能够快速响应市场需求,提供创新解决方案。选择合适的低代码平台,小成本组建一个专属于你的应用。 项目简介 这是一个…

竞赛 基于深度学习的人脸识别系统

前言 🔥 优质竞赛项目系列,今天要分享的是 基于深度学习的人脸识别系统 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🧿 更多资料, 项目分享: https://gitee.com/dancheng-senior/…

spring security教程(一)--认证

零.简介 【1】简介 【2】登录校验流程 【3】原理(入门的时候先了解一下就好) 一.思路分析 二.建表 确保你已经建立好一张用户表,并且引入springboot,mybatis,mp,slf4j等基础依赖。 即使你有多个角色你也可以将他们的…

安装社区版本OB

获取一键安装包 https://www.oceanbase.com/softwarecenter 离线安装 [admintest001 ~]$ tar -xzf oceanbase-all-in-one-*.tar.gz [admintest001 ~]$ cd oceanbase-all-in-one/bin/ [admintest001 bin]$ ./install.sh [admintest001 bin]$ source ~/.oceanbase-all-in-one/…

【人工智能】企业如何使用 AI与人工智能的定义、研究价值、发展阶段的深刻讨论

前言 人工智能(Artificial Intelligence),英文缩写为AI。 它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。人工智能是新一轮科技革命和产业变革的重要驱动力量。 📕作者简介&#x…

stm32学习-芯片系列/选型/开发方式

【03】STM32HAL库开发-初识STM32 | STM概念、芯片分类、命名规则、选型 | STM32原理图设计、看数据手册、最小系统的组成 、STM32IO分配_小浪宝宝的博客-CSDN博客  STM32:ST是意法半导体,M是MCU/MPU,32是32位。  ST累计推出了&#xff1a…

【NCRE 二级Java语言程序设计03】考试环境及考试过程概览

目录 前言一、考试环境介绍1.硬件环境2.软件环境 二、考试特别说明1.考试时间说明2.考试题型及分值 三、考试流程介绍1.登录考试系统2.考试答题界面3.答题交卷操作 总结 前言 📜本专栏主要是分享自己备考全国计算机二级Java语言程序设计所学心得体会、所搜集的资料信…

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

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

大数据快速入门开发环境篇:CentOS 7安装配置Hadoop大数据框架开发环境

注意:在开始安装之前,请确保您的CentOS 7系统已经正确安装和配置了Java。Hadoop需要Java来运行。 目录 一、下载与配置Hadoop框架:1.1、下载与环境变量设置1.2、XML配置文件Hadoop设置1.3、格式化HDFS 二、Hadoop 3.x版本中hdfs命令的问题解…

使用命令行(CMD)编译单Java文件

1.安装JDK JDK官网:https://www.oracle.com/java/technologies/downloads/ 选 Windows -> x64 MSI Instaler或者x64 Installer 安装成功后。 2.配置环境变量 按下Win键,搜索环境变量 添加JAVA_HOME系统环境变量,要指定类似这样的路径(…