这就是指令重排的精髓——在保证结果正确的前提下,重新安排操作顺序以提高效率。
// 源代码
int a = 1;
int b = 2;
// 可能被重排为:
int b = 2;
int a = 1; // 不影响最终结果
// 这两个操作无依赖,CPU可能并行执行
int x = calculateX(); // 耗时操作
int y = calculateY(); // 耗时操作
// 线程A
obj.fieldA = 1;
flag = true;
// 线程B可能先看到flag=true,后看到fieldA=1
虽然可以重新排序,但必须遵守:
数据依赖性:有依赖的操作不能重排
int a = 1;
int b = a + 1; // 必须保持顺序
as-if-serial语义:单线程执行结果不变
// 无论怎么重排,最终a=2,b=3
int a = 1;
int b = 2;
a = a + 1;
b = a + 1;
多线程happens-before规则(特殊约束)
// 源代码
public void demo() {
int a = 1;
int b = 2;
}
// 字节码(可能顺序)
0: iconst_2 // b=2
1: istore_2
2: iconst_1 // a=1
3: istore_1
instance = new Singleton();
// 可能被重排为:
// 1.分配内存 → 3.赋值引用 → 2.初始化对象
// 导致其他线程拿到未初始化的对象!
private static volatile Singleton instance;
@JCStressTest
@Outcome(id = "0", expect = ACCEPTABLE)
@Outcome(id = "1", expect = ACCEPTABLE_INTERESTING)
class ReorderingDemo {
int x;
boolean ready;
@Actor
void actor1() {
x = 1;
ready = true;
}
@Actor
void actor2() {
if (ready) {
System.out.println(x); // 可能输出0!
}
}
}
volatile关键字
volatile boolean flag = false; // 禁止前后指令重排
synchronized同步
synchronized(lock) {
// 块内不会与外部重排序
}
final字段
final Map config; // 正确构造后可见,且防止重排序
重排序类型 | 说明 | 示例 |
---|---|---|
Load-Load | 读操作重排 | 先读A后读B → 先读B后读A |
Store-Store | 写操作重排 | 先写A后写B → 先写B后写A |
Load-Store | 读后写重排 | 先读A后写B → 先写B后读A |
Store-Load | 写后读重排(最常见) | 先写A后读B → 先读B后写A |
指令重排就像厨师的"时间管理术"——在保证菜品(程序结果)正确的前提下,巧妙调整步骤顺序来提升效率,但多线程环境下需要volatile这样的"监督员"来防止翻车! ⚡