深入有序性:指令重排发生在哪些地方?¶
一、什么是指令重排¶
编译器和 CPU 为了优化性能,可能改变指令的执行顺序,但不影响单线程语义。
二、重排发生的三个地方¶
1. 编译器重排¶
javac / JIT 编译器在不改变单线程语义的前提下调整指令顺序。
2. CPU 指令级并行重排¶
CPU 不按程序顺序执行指令,而是按数据依赖乱序执行(Out-of-Order Execution)。
3. 内存系统重排¶
写缓冲器和失效队列导致其他核看到的顺序和实际写入顺序不一致。
即使 CPU 按顺序写,其他核看到的可能是乱序的。
三、单线程 vs 多线程¶
- 单线程:重排不影响结果(as-if-serial 语义)。
- 多线程:重排可能导致其他线程看到不一致的中间状态。
四、典型例子:单例 DCL¶
分三步: 1. 分配内存。 2. 初始化对象。 3. instance 指向内存。
JIT 重排成 1 → 3 → 2:
volatile 禁止这种重排。
五、happens-before¶
JMM 用 happens-before 规则定义什么不能重排:
- 程序顺序规则:单线程内前一个操作 happens-before 后一个。
- volatile 规则:volatile 写 happens-before 后续 volatile 读。
- 锁规则:unlock happens-before 后续 lock。
- 传递性。
关键点
重排在单线程下是透明的,多线程下是 bug 根源。volatile 和 synchronized 就是告诉编译器/CPU"这里不能乱"。