什么是 Java 中的原子性、可见性和有序性?

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(锁住整段代码)
  • AtomicIntegerAtomicLong 等原子类(CAS + 自旋)
  • LongAdderDoubleAdder(高并发分段累加,性能更好)

(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 强保证)
  • Lockunlock() / lock() 也有内存语义

(3)有序性(Ordering)

定义:程序代码的执行顺序与代码书写顺序一致(至少“看起来”一致)。

为什么会乱序? 有两种重排序:

  1. 编译器重排(javac / JIT)
  2. 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() 可能被重排成:

  1. 分配内存
  2. 把引用指向内存(此时对象还没初始化完)
  3. 调用构造方法

其他线程看到 instance != null,但对象字段还没初始化 → NPE 或逻辑错误。

解决:在 instance 上加 volatile(JDK 5+ 有效)

private static volatile Singleton instance;

volatile 禁止它前后的指令重排序 + 提供内存可见性。

3. 2026 年最常考的总结口诀

  • 原子性:管“做不做完”,防“半截操作”
  • 可见性:管“看不看得见”,防“缓存看不到更新”
  • 有序性:管“执行顺序对不对”,防“重排序搞乱逻辑”

一句话记住三者的关系

可见性是有序性的前提,原子性是可见性的保障,三者缺一不可才能真正线程安全。

最常用组合手段对比(面试爱问):

手段原子性可见性有序性使用场景建议
synchronized普通同步块、方法
volatile状态标志、单次发布
AtomicXXX计数器、引用更新
Lock需要中断、超时、条件队列
final不可变对象、构造函数安全发布

有想看某个具体例子(比如 volatile + Atomic 的组合、happens-before 规则细节、禁止重排序的内存屏障原理),可以继续问~

文章已创建 4665

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部