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

指令重排序

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

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 变量的重排序,保证多线程下执行顺序符合代码书写逻辑。

http://www.cnnetsun.cn/news/3408633.html

相关文章:

  • docker mysql主从安装-元一软件
  • 博客重构三次后的心得
  • [计算机入门] 键盘分区与高效操作指南
  • STM32存储革新实战 SPI总线驱动FRAM MB85RS16实现高速数据记录
  • 手写C++17无锁发布-订阅框架:环形缓冲区与内存序实战
  • 从旋转矢量到频谱图:深入解析傅里叶变换在图像滤波中的核心原理
  • 【ICer进阶指南】模拟IC设计:从理论到流片的艺术与工程实践
  • CC1020窄带射频收发器电路设计与PCB布局实战指南
  • TDA2E-17外设引脚配置实战:从架构解析到PCB设计避坑指南
  • Long — 中小学生综合 Web 学习平台
  • DNS协议实战:从报文捕获到权威解析【头歌】
  • ChatGPT写周报月报:从“凑字数”到“领导主动转发”的5步质变法(附可即用Prompt库)
  • 【项目实战】金融风控之信用评分卡模型
  • ubuntu22.04 ROS2 humble slam and navigation
  • 【Cortex-M内核篇】--复位向量表:从硬件加载到软件执行的启动密码
  • 顺义鼻咽喉气道哮喘中医特色诊疗医生列表
  • 具身智能:定义、特征与原理详解(7)
  • Java包管理与字符串操作实战:从项目结构到数据处理
  • 零代码搭建业务语义网络,业务人员也能参与企业知识建模
  • 【C++】从内存布局到精度取舍:深入解析float与double的底层差异
  • 每天认识一种投资品类:股票
  • 毕设项目分享 深度学习yolo11垃圾分类系统(源码+论文)
  • 硕士 / 博士论文能用免费查重吗?不收录文稿、准确度高的正规平台实测盘点
  • CVE-2025-59828漏洞解析:AI编码工具信任边界绕过与Yarn插件安全风险
  • PilotGo-plugin-container与其他容器监控工具对比:为什么它更适合你?
  • C++面向对象编程实践:用类封装计算器核心逻辑
  • TK1终端优化五步法:从卡顿到秒响应的实操指南
  • 国产代码大模型CLI工具选型与本地化实践指南
  • Qt/C++源码/地图控件/实时动态轨迹/历史轨迹回放/航线规划/性能强悍
  • 使用llama.cpp量化部署DeepSeek模型:从PyTorch到GGUF的完整实践