Java 并发编程中,原子性、可见性和有序性是三个核心概念。理解它们对于排查多线程 Bug 至关重要,绝大多数并发问题都能归因于这三者之一。

原子性
原子性意味着一个操作要么全部执行完成,要么完全不执行,中间不会被其他线程打断。以 i++ 为例,这行代码在底层其实是三步操作:读取变量值、加 1、写回内存。在多线程环境下,如果没有同步机制,多个线程可能同时读取到同一个旧值,导致最终结果不正确。
可见性
可见性是指当一个线程修改了共享变量,其他线程能够立即感知到这个变化。现代 CPU 拥有多级缓存(L1/L2/L3),线程通常优先读写自己的缓存。如果线程 A 修改了变量但数据还停留在本地缓存未刷回主内存,线程 B 读取到的就是旧值。
有序性
有序性要求程序执行的顺序与代码编写的顺序一致。为了优化性能,编译器和 CPU 会对指令进行重排序。单线程下这种重排序不会影响结果(as-if-serial 语义),但在多线程环境中,指令乱序可能导致逻辑错误。
下面通过双重检查锁定(DCL)的单例模式示例,看看这三个问题如何引发 Bug:
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 潜在问题点
}
}
}
return instance;
}
}
这段代码看起来没问题,但 instance = new Singleton() 实际上分三步:分配内存空间、初始化对象、将引用指向内存地址。CPU 或编译器可能将第 2 步和第 3 步重排序。如果发生重排序,另一个线程可能在对象未完成初始化时就获取到了引用,导致空指针异常或数据错乱。解决方案是给 instance 加上 volatile 关键字。
扩展知识
原子性的保障手段
Java 保证原子性主要靠锁和 CAS(Compare-And-Swap)。
synchronized 和 Lock 是最直接的手段,进入临界区的线程独占资源,其他线程阻塞等待。虽然简单可靠,但锁的开销较大,涉及线程切换和上下文保存。
CAS 是一种乐观锁策略,依赖 CPU 的 指令。例如 的 ,它会不断尝试比较当前值是否等于预期值,相等则更新,失败则重试。CAS 避免了线程阻塞,但在高竞争场景下会导致自旋消耗 CPU 资源。



