Java并发理论(一):指令重排序与数据依赖性

并发编程JMM系列之重排序和顺序一致性 前言 昨天我们接触到了什么是Java内存模型以及两种Java并发模型,并对JMM有了一些初步的认识和了解,我们在上节有提到JMM的重排序规则,但是讲的不详细,今天我们再重点聊下重排序这个东西,以及顺序一致性内存模型,OK,开始我们今天的并发之旅吧。 重排序要遵循的三个原则 重排序的目的是提高编译器和处理器的并行处理能力即并行度,但是在做重排序时,一般都会遵循下面3种原则: 数据依赖性原则:如果两个操作访问同一个变量,并且这两个操作中有一个操作是写操作,那么这两个操作之间就存在数据依赖性,上节我们 阅读详情


先附上思维导图,本篇重点介绍指令重排序,主要为了下篇的as-if-serial和happens-before规则。
在这里插入图片描述

1 为什么需要指令重排序?

现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取指、译码、访存、执行、写回等若干个阶段。然后,多条指令可以同时存在于流水线中,同时被执行。
指令流水线并不是串行的,并不会因为一个耗时很长的指令在“执行”阶段耗时很长时间,而导致后续的指令都卡在“执行”之前的阶段上。我们编写的程序都要经过优化后(编译器和处理器会对我们的程序进行优化以提高运行效率)才会被运行,优化分为很多种,其中有一种优化叫做重排序,重排序需要遵守as-if-serial规则和happens-before规则,重排序的目的是为了提升性能。

2 重排序的分类

  1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
  2. 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;
  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。

3 重排序的过程

在这里插入图片描述如图,1属于编译器重排序,而2和3统称为处理器重排序。这些重排序会导致线程安全的问题,一个很经典的例子就是DCL(双重检验锁)问题,这个在以后的文章中会具体去聊。针对编译器重排序,Java内存模型(JMM)的编译器重排序规则会禁止一些特定类型的编译器重排序;针对处理器重排序,编译器在生成指令序列的时候会通过插入内存屏障指令来禁止某些特殊的处理器重排序。

4 重排序对多线程的影响

先看以下一段代码:

/**
 * @author Carson
 * @date 2020/5/28 20:07
 */
public class ReorderService {
    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("ReorderService-%d").build();

    private static ExecutorService executorService = new ThreadPoolExecutor(5, 20, 1000L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());

    public static void main(String[] args) {
        final ReorderModel model = new ReorderModel();
        int counter=5;
        for (int i = 0; i < counter; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    model.writer();
                }
            });
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    model.reader();
                }
            });
        }
    }
}

class ReorderModel {
    private int a = 0;
    private boolean flag = false;

    void writer() {
        //操作1
        a = 1;
        //操作2
        flag = true;
    }

    void reader() {
        //操作3
        if (flag) {
            //操作4
            int i = a * a;
            System.out.println(i);
        } else {
            System.out.println("少侠露飞");
        }
    }
}

flag为标志位,表示a有没有被写入,当A线程执行 writer 方法,B线程执行 reader 方法,线程B在执行4操作的时候,能否看到线程A对a的写入操作?少侠先不做出回答,实践才是检验真理的唯一标准,让我们跑起代码看看结果。
我们进行了五次循环输出,结果每次都不尽相同,这里贴出其中一次的运行结果:

1
1
1
少侠露飞
1

所以对于上述问题,答案已经很明显了。但这是为什么呢?
实际上由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序。
如果操作1和操作2做了重排序,程序执行时,线程A首先写标记变量 flag,随后线程 B 读这个变量。由于条件判断为真,线程 B 将读取变量a。此时,变量 a 还根本没有被线程 A 写入,在这里多线程程序的语义被重排序破坏了!

5 数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:

名称代码示例说明
写后读a = 1;b = a;写一个变量之后,再读这个变量。
写后写a = 1;a = 2;写一个变量之后,再写这个变量。
读后写a = b;b = 1;读一个变量之后,再写这个变量。

注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

点点关注,不会迷路

Java 微服务之间的数据依赖问题解决方案 消息队列:利用消息队列进行异步通信。:使用API网关统一处理请求。事件驱动:通过发布订阅模型实现事件通知。数据库事务:在某些情况下,可以考虑跨服务的分布式事务。通过上面的例子,我们看到了如何使用消息队列和API Gateway来解决微服务之间的数据依赖问题。这两种方法各有优劣,可以根据实际需求选择适合的方式。好啦,今天的分享就到这里。我们下次再见!👋🏻。 阅读详情

相关推荐

树莓派5首次开机保姆级教程(无显示器通过VNC连接树莓派桌面)

选择 Interface Options > VNC > Yes 启用服务。2.在Windows电脑上安装VNC软件。重启树莓派:sudo reboot。3.使用VNC远程访问树莓派桌面。

qq_34184505的博客 2350

指令重排序

什么是指令重排序? 此内容来自公众号《我们都是小青蛙》,本文章只作为学习笔记。 public class ReaderThread { private static boolean stop=false; private static int number=0; public static void main(String[] args){ Thread t=new Thread(new Runnable(){ @Override

qq_30864125的博客 889

华为昇腾 910B 实战:DeepSeek-R1 蒸馏模型高效部署与性能调优全解析

本文详细解析了在华为昇腾910B硬件平台上高效部署DeepSeek-R1蒸馏系列模型(14B/32B版本)的完整实战流程。内容涵盖从MindIE环境搭建、镜像获取、模型准备,到服务配置、内存参数调优、性能测试及常见问题排查,为开发者提供了基于昇腾910B进行大模型部署与优化的全链路指南。

weixin_28235513的博客 210

指令重排序的原理

这就存在一个问题,CPU的运算速度要远快于对内存的操作,将工作内存数据写入主内存即物理内存时,如果两个CPU同时需要写入同一块内存区域,这就需要一个CPU等待另一个CPU写入完成后再写,这就造成了CPU的浪费,而这种情况在单核CPU是不存在的,所以需要指令重排序。在并发程序中,程序员会特别关注不同进程或线程之间的数据同步,特别是多个线程同时修改同一变量时,必须采取可靠的同步或其它措施保障数据被正确地修改,这里的一条重要原则是:不要假设指令执行的顺序,你无法预知不同线程之间的指令会以何种顺序执行。

WC949464367的博客 853

【技术拆解】Illumina边合成边测序(SBS)核心流程全解析 | 从文库构建到数据分析

本文深入解析了Illumina边合成边测序(SBS)技术的完整流程。从DNA样本的文库构建(片段化、加接头、扩增),到Flowcell上的簇生成(桥式PCR),再到核心的边合成边测序(SBS)循环(荧光标记、可逆终止),最后简述了从原始数据到生物学洞见的数据分析步骤,为理解这一主流高通量测序技术提供了清晰的框架。

weixin_29201859的博客 536

基于STM32的电子时钟设计

基于STM32的电子时钟设计1、设计目的2、设计具体要求3、proteus仿真图4、程序代码 1、设计目的 (1)掌握使用KEIL MDK5进行软件开发的具体流程; (2)掌握硬件电路的设计方法; (3)掌握STM32微控制器的程序设计及代码编写方法。 2、设计具体要求 (1)通过STM32自带的实时时钟功能获取时间信息; (2)在OLED显示屏上显示日期、时、分、秒等时间信息; (3)通过按键可对时间信息进行修改; (4)具有闹钟设定功能; (5)加分项:自行增加其他功能。 3、proteus仿真图 4

唯一的博客 2万+

JAVA基础篇-数据依赖性含义

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分为下列3种类型,如表3-4所示。 上面3种情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。 前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。 这里所说的数据依赖性仅针对...

随便起个不重复的名字 4204

AI应用架构师总结:智能问答系统用户意图识别的8种方法与实践!

意图识别是智能问答系统的大脑入口,其技术水平决定了系统能否准确捕捉用户的核心诉求。本文系统剖析了从基础规则到前沿PLM的8种核心意图识别方法透明简单,冷启动快,维护成本高。中等规模场景基线,依赖特征工程。利用词义和局部/序列信息,训练成本高。当前最优精度,需大量资源。添加意图灵活,冷启动友好,精度可能略逊。提升鲁棒性,系统变复杂。解决歧义和自然交互的关键(多轮)。增强专业领域理解和推理能力。成功的意图识别模块需要:精准的意图定义(Schema)是前提!

AI云原生与云计算技术学院 1632

高性能CPU的秘密—新技术篇

高性能CPU的秘密—新技术篇 ● Johan De Gelas(摘自电脑报 双木译)   Rise MP6是X86 CPU中第一款具有真正双FPU流水线的CPU,那么,它的性能比PⅡ好吗?如果你读过有关Rise MP6的评测报告,你就知道,MP6的FPU性能同使用单条FPU流水线的Pentium MMX差不多。那么,K7的表现又将怎样呢?它的3条FPU流水线在运行实际程序时只能提高一丁点儿性能吗

蝈蝈俊.net 2781

从零训练Qwen3-VL

如果你未来想做的是「在公开权重上继续做大规模多模态 pretrain(比如换数据、换目标函数)」而不是在完全随机初始化上起步,这两类代码会比 Open-Qwen2VL 更直接。参考 Qwen2-VL / Qwen2.5-VL 披露的流程,可以大致推测 Qwen3-VL 也是类似的三阶段:(直接在学术算力(8×A100-40G)上从零预训练了一个 2B 参数的多模态 Qwen2-VL 风格模型:(的配置和训练脚本,理解它是如何把「任意 ViT + 任意 LLM + 任意图文数据」拼在一起的。

u013250861的博客 563

【Bug已解决】Codex Desktop 报错 Computer Use 插件不可用的解决方案

方案适用场景推荐指数让 Codex 自查修复首选的快速尝试方式⭐⭐⭐⭐⭐清理插件缓存重启明确怀疑是本地缓存损坏⭐⭐⭐⭐彻底卸载重装前两个方案无效时的进阶方案⭐⭐⭐⭐检查安全软件拦截企业电脑/装有严格安全软件的环境⭐⭐⭐确认账号权限范围排除功能本身未开放的可能性⭐⭐⭐Codex Desktop 报Computer Use 插件不可用的本质是客户端内置的插件资源在本地未被正确解包或注册到插件市场索引中,而不是真的缺少安装包。优先尝试让 Codex 自身检测并修复本地插件环境。

一键难忘的博客 5981

重排序与数据依赖性

上一篇博客我们了解了Java内存模型,下面我们来了解一下重排序和数据依赖性的相关知识。 为什么需要重排序 现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段。然后,多条指令可以同时存在于流水线中,同时被执行。 指令流水线并不是串行的,并不会因为一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上。我们编写的程序都...

ThinkWon的博客 8618

图解联合概率密度、边缘概率密度、条件概率密度之间的关系

简要图解联合概率密度、边缘概率密度、条件概率密度之间的关系

一个搬运知识的笨小孩 2万+

Java JVM(十二):指令重排序

介绍指令重排序的相关知识。

Allen 2万+

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Java并发编程之指令重排序

在我们面试过程中,通常避免不了会被问到什么是指令重排序?本文就这个问题进行探索。 重排序前言一、重排序种类二、happens-before三、重排序1.数据依赖性2. as-if-serial语义3.程序顺序规则4.重排序在多线程中的影响 前言 在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。 一、重排序种类 在java语言中,重排序分为3种。 编译器优化的重排序。编译器在不改变单线程程序的语义前提下,可以重新安排语句的执行顺序。 指令级并行的重排序。现在处理器采用了指令集并行技术,

weixin_44692700的博客 4856

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

java并发编程(4) 有序性-指令重排序

文章目录前言1. 概念2. 数据依赖性3. as-if-serial1. 概念和理解2. 例子 前言 这一系列资料基于黑马的视频:java并发编程,这篇文章中介绍指令重排序以及如何解决这个问题。同时参考了《Java并发编程这本书》,程序员必备 1. 概念 重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。比如下面的一段代码: //重排序前 int i = 0; int j = 1; //重排序后 int j = 1; int i = 0; 这段赋值的代码重排序之后可能顺序

Jwasx的博客 2018

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

JVM(十一)Java指令重排序

一、指令重排序 我们先看一下下面的代码会输入 什么样的结果? public class PossibleReordering { static int x = 0, y = 0; static int a = 0, b = 0; public static void main(String[] args) throws InterruptedException { Thread

keep_trying的专栏 1万+

并发编程(十四)-----重排序与数据依赖性

为什么需要重排序 现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段。然后,多条指令可以同时存在于流水线中,同时被执行。 指令流水线并不是串行的,并不会因为一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上。我们编写的程序都要经过优化后(编译器和处理器会对我们的程序进行优化以提高运行效率)才会被运行,优化分为很...

qq_35854212的博客 539

Java并发理论总结

文章目录JMM的设计happens-before与JMM的关系需要关注的问题 在多线程中稍微不注意就会出现线程安全问题,那么什么是线程安全问题?为什么会出现线程安全问题?出现线程安全的问题一般是因为主内存和工作内存数据不一致性和重排序导致的,而解决线程安全的问题最重要的就是理解这两种问题是怎么来的,那么,理解它们的核心在于理解Java内存模型(JMM)。 为了性能优化,还会涉及到重排序与数据依赖性...

ThinkWon的博客 8328

多线程之指令重排序

1、首先为何要指令重排序(instruction reordering)? 编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。 也就是说,对于下面两条语句: int a = 10; int b = 20; 在计算机执行上面两句话的时候,有可能第二条语句会先于第一条语句执行。所以,千万不要随意假设指令执行的顺序。 2、是不是所有的语句的执行顺

Yuwen's Hero 1万+

聊聊指令重排序

java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的**执行顺序可以与代码顺序不一致,此过程叫指令的重排序。**

qq_25409421的博客 3万+

Java并发(三):重排序与内存一致性

其实happens-before只是一个规则,,而happens-before的实现其实也就是重排序前面我们已经简单了解过重排序,关键在于是为了优化性能,而且要注意,每个线程都会发生重排序,因为。

2401_89694232的博客 913

JVM指令重排序

指令重排序是一种在编译器和处理器级别发生的优化过程,它改变了程序原有的指令执行顺序。这种优化可以在多个层面上发生,包括编译器优化、即时编译优化(JIT),以及处理器层面的优化。指令重排序是一种复杂但非常有效的优化技术。它使得处理器能够更加智能地利用自身的各种资源,如流水线、缓存和并行执行单元,从而提高整体性能。然而,这种优化也带来了额外的挑战,尤其是在多线程编程中,开发者需要对这种机制有所了解,以确保程序的正确性和效率。

斌斌(Java) 2180

java并发之重排序问题

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型。1编译器优。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。2级。现代处理器采用了指令级并行技术ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。3统。由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

weixin_69039908的博客 820

Java - 指令重排序

为什么会发生指令重排序(instruction reordering) 编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。 也就是说对于下面的语句: int a = 10; int b = 20; 在计算机执行上面两句话的时候,有可能第二条语句会先于第一条语句执行。 然而并不是所有的指令都能重排,重排需要基于数据依赖性。 数据依赖性 如果两个操作访问...

用时间去蜕变 719
上一篇: Java并发编程(五):synchronized原理详解及锁优化
下一篇: Java并发理论(二):as-if-serial规则和happens-before规则详解
少侠露飞
博客等级 码龄9年 214粉丝 117原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值