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

在 Java 并发编程中,原子性(Atomicity)、可见性(Visibility)和有序性(Ordering) 是 Java 内存模型(Java Memory Model, 简称 JMM)中保障多线程程序正确执行的三大核心特性。它们分别解决并发中的三类关键问题:操作的完整性、变量的可见性和执行顺序的可控性


一、原子性(Atomicity)

定义:

原子性是指一个操作是不可中断的,要么全部执行成功,要么全部不执行,不会在执行过程中被其他线程打断。

示例:

在 Java 中,像 int i = 1; 这种基本赋值操作是原子性的。但像 i++ 这种复合操作(读取、加1、写入)就不是原子的,因为它由多个步骤组成。

int i = 0;
i++; // 不是原子操作,包含三个步骤:读取i、加1、写入i

如何保证?

  • 使用 synchronized 关键字
  • 使用 java.util.concurrent.atomic 包下的原子类(如 AtomicIntegerAtomicLong
  • 使用 Lock(如 ReentrantLock

二、可见性(Visibility)

定义:

可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。

为什么会出现不可见?

因为每个线程都有自己的工作内存(缓存),线程对变量的操作通常是在工作内存中进行的,而不是直接操作主内存。如果没有同步机制,其他线程可能读取到的是过期的值。

如何保证?

  • 使用 volatile 关键字(强制读写主内存)
  • 使用 synchronized(进入同步块前清空工作内存,退出时刷新到主内存)
  • 使用 Lock(同上)
  • 使用 java.util.concurrent 包下的并发工具类

三、有序性(Ordering)

定义:

有序性是指程序执行的顺序按照代码的先后顺序执行。但在并发环境下,编译器和处理器为了优化性能,可能会对指令进行重排序(Instruction Reordering),这在多线程下可能导致错误。

举例:

int a = 1;
int b = 2;

这两个赋值操作可能被重排序为:

int b = 2;
int a = 1;

虽然在单线程中不影响结果,但在多线程中可能会导致其他线程观察到不一致的状态。

如何保证?

  • 使用 volatile(禁止指令重排序)
  • 使用 synchronized(建立内存屏障)
  • 使用 final 关键字(final变量在构造函数中赋值不会被重排序)
  • 使用 java.util.concurrent 包中的并发工具

三者关系总结:

特性作用保证手段
原子性操作不可中断,要么全做,要么全不做synchronizedAtomicIntegerReentrantLock
可见性一个线程对共享变量的修改对其他线程可见volatilesynchronizedLock
有序性禁止指令重排序,保证执行顺序volatilesynchronizedfinal

实际应用示例

public class VisibilityExample {
    private volatile boolean flag = false;

    public void toggle() {
        flag = true; // volatile保证可见性和有序性
    }

    public void loop() {
        while (!flag) {
            // 等待flag变为true
        }
        System.out.println("Flag is true now.");
    }
}

在这个例子中,volatile 保证了:

  • 可见性:一个线程修改了 flag,另一个线程能立即看到。
  • 有序性:防止编译器/处理器对 flag = true 的赋值进行重排序。

总结

  • 原子性:保证操作的完整性,防止中途被中断。
  • 可见性:保证线程之间对共享变量状态的同步。
  • 有序性:防止指令重排序导致的执行顺序混乱。

理解这三大特性是掌握 Java 并发编程的关键,它们共同构成了 Java 内存模型的基础,帮助开发者写出正确、高效、线程安全的程序。

THE END
喜欢就支持一下吧
点赞10 分享