指令重排是编译器和cpu为优化性能主动调整无关指令顺序的行为,单线程下符合as-if-serial语义故安全,多线程下因可见性与有序性破坏导致npe、半初始化对象等问题。
指令重排到底是什么,为什么单线程没问题、多线程就崩了
指令重排不是 bug,是编译器和 CPU 主动做的性能优化:它们会把看似“无关”的读写操作调换顺序,只要最终单线程执行结果不变(as-if-serial 语义)。比如 flag = true 和 resource = new Resource() 在无依赖时,JIT 可能先执行赋值再构造对象——这在单线程里完全安全,但放到多线程里,另一个线程可能看到 flag == true 却拿到一个未初始化完成的 resource,直接 NPE 或逻辑错乱。
常见错误现象:
- 懒汉式单例在多线程下返回 null 或半初始化对象
- 状态标志(如
isRunning)被设为 false 后,工作线程仍死循环不退出 - 配置加载后,部分线程读到旧值,部分读到新值
volatile 怎么靠内存屏障“锁住”执行顺序
volatile 不是加锁,而是插入内存屏障(Memory Barrier),强制约束指令的可见性和顺序。它在底层对应 CPU 的 lock 前缀指令(x86)或 ldarx/stwcx.(PowerPC),并配合 MESI 缓存一致性协议生效。
关键约束有三类:
立即学习“Java免费学习笔记(深入)”;
-
volatileWrite()前插入storeStoreBarrier:确保它前面所有普通写操作已落地,不会被拖到 volatile 写之后 -
volatileWrite()后插入storeLoadBarrier:防止后续任意读/写被提前到该 volatile 写之前 -
volatileRead()后插入loadLoadBarrier和loadStoreBarrier:保证该读取之后的操作,不会被重排到读之前
简单说:volatile 让 JVM 和 CPU “不敢动”它附近的指令顺序,从而守住你代码里隐含的因果逻辑。