指令重排重排的是无数据依赖的指令顺序,是编译器和CPU为提升单线程性能做的合法优化;volatile通过内存屏障禁止其读写附近的重排,synchronized和Lock借助Happens-Before和内存屏障保证临界区有序。

指令重排到底在重排什么
它不是随意打乱代码,而是编译器和处理器在确保单线程行为不变的前提下,对**无数据依赖的指令**调整执行顺序。比如:
- 编译器优化重排:把内存写入和计算指令交错,减少缓存等待;
- 指令级并行重排:CPU发现两条指令不争抢同一个寄存器或数据,就让它们“并行跑”;
- 内存系统重排:写操作先落缓存、后刷主存,读操作优先查缓存——看起来像乱序,实则是缓存机制导致的“伪重排”。
为什么单线程安全,多线程就出问题
关键在as-if-serial语义:只要最终结果和按源码顺序执行一致,重排就合法。但这个“结果”只针对单个线程可见。一旦涉及共享变量,问题就浮现:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- 线程1执行
a = 1; flag = true;,编译器可能把flag = true提前; - 线程2看到
flag == true就去读a,却可能拿到旧值0; - 这不是编译器或CPU错了,而是它们没义务保证跨线程的观察一致性。
哪些操作会被重排?哪些不会
是否重排取决于是否存在数据依赖:
-
真依赖(必须保持):如
x = a + b; y = x * 2;,第二句依赖第一句结果,不可重排; -
反向依赖(通常不重排):如
a = b + c; b = 1;,后句改了前句用的b,一般保留顺序; -
输出依赖(可重排):如
a = 1; a = 2;,两次写同一变量,编译器可能合并或调整,只要最终a == 2即可。
怎么约束重排:屏障与语言机制
靠程序员显式干预,告诉编译器/CPU:“这里不能越界”:
-
Java 中
volatile:禁止对该变量的读写与其他普通读写重排,并强制刷新缓存; -
Java 中
synchronized:加锁/解锁处自动插入内存屏障,锁内指令不会被拖到外边; -
C++ 中
std::atomic+ 内存序:如memory_order_acquire/release精确控制哪段顺序必须维持; -
底层屏障指令:如
mfence(全屏障)、lfence(读屏障),直接作用于CPU流水线。

















