有序性指多线程下程序实际执行顺序可能与源码顺序不一致,主因是编译器、处理器和内存系统为提升性能进行的指令重排;volatile通过内存屏障禁止特定重排,保证写前读后的执行顺序。

Java 中的“有序性”不是指代码写成什么样就一定按什么样执行,而是指在多线程环境下,**程序实际执行顺序可能和源码顺序不一致**——这种不一致主要来自编译器和处理器为提升性能做的指令重排。
为什么会有指令重排?
重排不是 bug,是主动优化:
- 编译器重排:javac 或 JIT 编译器在生成字节码或机器码时,会把没有数据依赖的语句调换顺序,比如先算 b 再算 a,只要不影响单线程结果;
- 处理器重排:CPU 利用流水线、写缓冲区、乱序执行等机制,让多条指令并行处理。例如把 flag = true 提前到 a = 1 之前发出去,只要它不破坏单线程逻辑;
- 内存系统重排:由于 CPU 缓存、写缓冲区的存在,其他线程看到的写入顺序,可能和你代码里写的顺序不同。
重排只对单线程“安全”,但会破坏多线程协作
Java 遵守 as-if-serial 语义:单线程下,不管怎么重排,结果必须和按源码顺序执行一样。但多线程下,问题就来了:
比如这两句:
flag = true;
它们没数据依赖(flag 不依赖 a),所以可能被重排成先写 flag,再写 a。另一个线程看到 flag == true,却读到 a 还是旧值(比如 0),就出错了。
怎么控制重排?靠 JMM 的约束机制
Java 内存模型(JMM)不禁止所有重排,而是通过规则限制“危险重排”:
- volatile 变量写:在其之前的任何操作,都不能被重排到它之后;
- volatile 变量读:在其之后的任何操作,都不能被重排到它之前;
- 锁的获取与释放、线程启动/终止、构造器结束等,都隐含 happens-before 关系,天然禁止某些重排;
- JVM 在生成指令时插入 内存屏障(如 LoadStore、StoreLoad),告诉 CPU “这里不能跨屏障重排”。
典型陷阱:对象构造未完成就被发布
像双重检查单例中 new Singleton() 这一步,其实分三步:
- 分配内存;
- 初始化对象(调构造方法);
- 把引用赋给静态变量 instance。
后两步可能被重排:引用提前写入,但对象还没初始化完。另一个线程拿到非 null 的 instance,一用就 NullPointerException。加 volatile 修饰 instance 就能禁止这种重排。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











