news 2026/7/15 20:28:38

指令重排序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
指令重排序

一、为什么会发生指令重排序?

CPU、编译器不会严格按照你写代码的顺序一行一行执行:

  1. 有些指令阻塞等待(读写内存、IO),CPU 闲着;
  2. 编译器 / CPU 调整指令顺序,把能并行执行的指令提前跑,充分利用硬件资源,提升吞吐。

前提:单线程下,重排不能改变最终执行结果,这是重排序遵守的基础规则(as-if-serial 语义)。

举个简单例子:

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

编译器可以重排成:

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

单线程结果完全一样,无任何影响,所以允许重排。

二、三种重排序来源

1. 编译器优化重排序(JIT 编译器)

JIT 在编译字节码为机器码时,调整语句执行顺序,消除冗余、优化分支、合并读写。

2. CPU 指令重排(处理器乱序执行)

CPU 有多级流水线、多个运算单元。 一条指令读内存很慢,CPU 不会干等,会先执行后面不依赖它数据的指令,也就是乱序执行。

3. 内存系统重排序(缓存、写缓冲区)

每个 CPU 核心有独立高速缓存、写缓冲,写操作不会立刻刷入主存,写入缓冲延时刷新,宏观上看起来读写顺序颠倒。

三、核心规则:as-if-serial 语义

不管怎么重排序,单线程程序的执行结果不能改变。 有数据依赖的两行代码,绝对不能重排:

  • 写后读:a=1; b=a;不能颠倒;
  • 读后写:b=a; a=2;不能颠倒;
  • 写后写:a=1; a=2;不能颠倒。

无数据依赖的代码,才允许随意调换顺序。

四、重排序带来的多线程安全问题(重点考点)

as-if-serial 只保证单线程正确,多线程之间没有这个约束,会出现逻辑错乱,最经典例子:DCL 双重检查锁单例。

instance = new Singleton();

这一行代码底层拆成三步:

  1. 分配一块内存空间;
  2. 在内存中初始化 Singleton 对象(构造函数赋值);
  3. 将 instance 引用指向这块内存地址。

这三步不存在数据依赖,编译器 / CPU 可以重排顺序为:1 → 3 → 2。

多线程故障场景:

  1. 线程 A 执行 instance=new Singleton (),重排执行 1 分配内存、3 赋值引用,还没执行初始化 2
  2. 此时线程 B 进入判断if(instance == null),发现 instance 不为空;
  3. 线程 B 直接使用 instance,拿到一个未初始化完成的半空对象,出现空指针 / 数据错乱。

这就是指令重排序导致的并发 bug,解决方案:用volatile修饰 instance,插入内存屏障禁止这三步重排。

五、volatile 如何禁止指令重排序:内存屏障 Memory Barrier

volatile 会在读写前后插入对应屏障,限制指令上下穿越屏障重排:

  1. volatile 写操作:StoreStore 屏障 + StoreLoad 屏障 所有 volatile 写前面的读写,不能重排到 volatile 写之后; volatile 写不能和后面的 volatile 读重排。
  2. volatile 读操作:LoadLoad 屏障 + LoadStore 屏障 volatile 读后面的读写,不能重排到 volatile 读之前。

简单记: 写 volatile:前面代码不许后移; 读 volatile:后面代码不许前移; 彻底阻断跨 volatile 变量的指令乱序。

六、补充:synchronized 也能防止重排

同步块整体具备原子性、有序性,同步代码内部的指令不会跑到锁外面,锁外代码也不会进入锁内,天然屏蔽跨同步区的重排序。

七、精简总结口述版

指令重排序是 JIT 编译器、CPU、缓存系统为优化性能打乱代码执行顺序;单线程遵循 as-if-serial 语义结果不变,但多线程无保护时会出现逻辑错乱,典型场景是 DCL 单例拿到未初始化对象;volatile 通过插入内存屏障禁止跨 volatile 变量的重排序,保证多线程下执行顺序符合代码书写逻辑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 20:23:39

docker mysql主从安装-元一软件

1. 首先拉去镜像 docker pull mysql:5.7 2. 运行镜像 主要将日志 存储文件, 配置文件 映射到主机上 docker run -p 13307:3306 --name mysql-master \ -v /mydata/mysql-master/log:/var/log/mysql \ -v /mydata/mysql-master/data:/var/lib/mysql \ -v /mydata/mys…

作者头像 李华
网站建设 2026/7/15 20:21:42

博客重构三次后的心得

我的博客叫「云深不知处」,跑了快一年了。今天想复盘一下:它做了三次重构,每次都改了什么、为什么改。 第一版:能跑就行 最早搭博客的时候,我只想三件事: 有自己的域名(blog.wuxiannet.com&am…

作者头像 李华
网站建设 2026/7/15 20:18:37

[计算机入门] 键盘分区与高效操作指南

1. 键盘分区基础认知 键盘作为人机交互的核心工具,其分区设计暗藏玄机。我第一次接触机械键盘时,曾对着密密麻麻的按键发懵——直到发现所有键盘都遵循着相似的分区逻辑。主键盘区占据中央位置,包含字母、数字和基础功能键;上方是…

作者头像 李华
网站建设 2026/7/15 20:18:35

STM32存储革新实战 SPI总线驱动FRAM MB85RS16实现高速数据记录

1. FRAM技术优势与MB85RS16特性解析在嵌入式系统开发中,数据存储方案的选择直接影响系统性能和可靠性。传统FLASH存储器虽然成本低廉,但其写入前必须擦除整个扇区的特性(通常4KB起步),导致实时数据记录时产生高达毫秒级…

作者头像 李华
网站建设 2026/7/15 20:18:24

手写C++17无锁发布-订阅框架:环形缓冲区与内存序实战

1. 项目概述:为什么一个“简单”的发布-订阅程序值得花一整天去写透在工业级机器人系统、自动驾驶中间件、实时金融行情分发,甚至现代嵌入式设备的固件更新机制里,“发布-订阅”(Publish-Subscribe)从来不是教科书里的…

作者头像 李华
网站建设 2026/7/15 20:14:03

从旋转矢量到频谱图:深入解析傅里叶变换在图像滤波中的核心原理

1. 傅里叶变换:从空间域到频域的魔法桥梁第一次听说傅里叶变换时,我完全无法理解为什么要把好好的图像变成一堆看不懂的"波纹"。直到后来在项目中尝试用它去除CT扫描图像中的噪声,才发现这个数学工具简直是图像处理的"瑞士军刀…

作者头像 李华