Java 中的原子性、可见性、有序性 是并发编程三大核心特性(也叫线程安全的三大基石),直接决定了多线程环境下程序是否会出现“诡异”行为。
下面用最清晰的方式解释这三个概念、为什么会丢失、怎么保证,以及常见实现手段(2026视角,JDK 8~21 行为一致)。
1. 三大特性对比表(最常背的面试版本)
| 特性 | 通俗定义 | 丢失的典型表现 | Java 中最常见的保证手段 | 是否由 JMM 强制保证 |
|---|---|---|---|---|
| 原子性 | 操作要么全做完,要么全不做,不可分割 | i++ 变成 i=0 → i=1(而不是 i=2) | synchronized / Lock / AtomicXXX / CAS | 否(只保证部分操作) |
| 可见性 | 一个线程改了共享变量,其他线程马上能看到 | 线程A改flag=true,线程B永远看不到 | volatile / synchronized / Lock / final(部分场景) | 部分(volatile 强制) |
| 有序性 | 代码的执行顺序 ≈ 写的顺序(禁止随意重排) | 初始化没完成就看到对象(DCL单例失效) | volatile / synchronized / happens-before 规则 | 部分(as-if-serial + JMM规则) |
2. 逐个拆解(带经典反例)
(1)原子性(Atomicity)
定义:一个或多个操作在 CPU 执行的过程中不允许被打断,要么全部成功,要么全部失败。
最经典的反例 —— i++ 不是原子操作(高中生级别常考)
int i = 0;
void increment() {
i++; // 实际是三步:1.读 2.加1 3.写回
}
多线程执行 10000 次 increment(),结果往往不是 10000。
为什么三步?(字节码视角)
getfield #2 // 读 i
iconst_1
iadd
putfield #2 // 写回 i
如何保证原子性?
synchronized/ReentrantLock(锁住整段代码)AtomicInteger、AtomicLong等原子类(CAS + 自旋)LongAdder、DoubleAdder(高并发分段累加,性能更好)
(2)可见性(Visibility)
定义:一个线程对共享变量的修改,对后续访问该变量的其他线程立即可见。
经典反例 —— while 死循环不退出
volatile boolean running = true; // 注释掉 volatile 就会死循环(很多机器上)
new Thread(() -> {
while (running) { /* 空转 */ }
System.out.println("stopped");
}).start();
Thread.sleep(100);
running = false; // 主线程改了,子线程不一定马上看到
原因:
现代 CPU + JVM 有工作内存(本地缓存),线程不一定立刻把修改刷回主内存,其他线程也不一定立刻从主内存重新加载。
保证可见性的手段:
volatile:最轻量、最直接(写后立即刷回主存,读时强制从主存取)synchronized:进入/退出 monitor 时自动做内存屏障(flush + invalidate)final:构造器结束前写 final 字段,对其他线程可见(JMM 强保证)Lock的unlock()/lock()也有内存语义
(3)有序性(Ordering)
定义:程序代码的执行顺序与代码书写顺序一致(至少“看起来”一致)。
为什么会乱序? 有两种重排序:
- 编译器重排(javac / JIT)
- CPU 指令级重排序(Store Buffer、Invalidate Queue 等)
经典反例 —— 双重检查锁(DCL)单例的经典错误写法
class Singleton {
private static Singleton instance; // 没 volatile
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 非原子!可能重排序
}
}
}
return instance;
}
}
问题:new Singleton() 可能被重排成:
- 分配内存
- 把引用指向内存(此时对象还没初始化完)
- 调用构造方法
其他线程看到 instance != null,但对象字段还没初始化 → NPE 或逻辑错误。
解决:在 instance 上加 volatile(JDK 5+ 有效)
private static volatile Singleton instance;
volatile 禁止它前后的指令重排序 + 提供内存可见性。
3. 2026 年最常考的总结口诀
- 原子性:管“做不做完”,防“半截操作”
- 可见性:管“看不看得见”,防“缓存看不到更新”
- 有序性:管“执行顺序对不对”,防“重排序搞乱逻辑”
一句话记住三者的关系:
可见性是有序性的前提,原子性是可见性的保障,三者缺一不可才能真正线程安全。
最常用组合手段对比(面试爱问):
| 手段 | 原子性 | 可见性 | 有序性 | 使用场景建议 |
|---|---|---|---|---|
| synchronized | 有 | 有 | 有 | 普通同步块、方法 |
| volatile | 无 | 有 | 有 | 状态标志、单次发布 |
| AtomicXXX | 有 | 有 | 有 | 计数器、引用更新 |
| Lock | 有 | 有 | 有 | 需要中断、超时、条件队列 |
| final | — | 有 | 有 | 不可变对象、构造函数安全发布 |
有想看某个具体例子(比如 volatile + Atomic 的组合、happens-before 规则细节、禁止重排序的内存屏障原理),可以继续问~