一、原子性(Atomicity)

定义

一个操作要么全部执行成功,要么全部不执行,不会被线程切换打断。

举例

i++;

看似一行代码,其实包含 3 个步骤:

  1. 读取 i
  2. i + 1
  3. 写回 i

在多线程下可能出现:

线程A 读取 0
线程B 读取 0
线程A 写回 1
线程B 写回 1

最终结果是 1,而不是 2 —— 这叫 竞态条件(Race Condition)


如何保证原子性?

  1. synchronized
  2. Lock
  3. AtomicInteger
  4. CAS(Compare And Swap)

例如:

AtomicInteger i = new AtomicInteger(0);
i.incrementAndGet();

底层使用 CAS + 自旋 保证原子性。


二、可见性(Visibility)

定义

一个线程对共享变量的修改,其他线程能够立即看到。


为什么会出现可见性问题?

因为 Java 内存模型(JMM):

  • 线程有自己的 工作内存
  • 共享变量在 主内存

线程操作流程:

主内存 → 工作内存 → 运算 → 写回主内存

如果线程A修改变量,但没及时刷新到主内存,线程B就看不到。


典型问题

boolean flag = true;

Thread A:
while(flag) {
}

Thread B:
flag = false;

可能死循环!

因为线程A可能一直用自己的缓存。


如何保证可见性?

1. volatile

volatile boolean flag = true;

作用:

  • 保证可见性
  • 禁止指令重排序(部分)

但不保证原子性!


2. synchronized

  • 解锁前必须刷新主内存
  • 加锁时必须读取主内存

三、有序性(Ordering)

定义

程序执行顺序按照代码顺序执行。

问题是:CPU 和 JVM 会进行指令重排序优化。


指令重排序的三种情况

  1. 编译器优化重排
  2. CPU 指令重排
  3. 内存系统重排

举个经典例子(单例 DCL)

instance = new Singleton();

这行代码可能会被重排成:

  1. 分配内存
  2. 指向内存
  3. 初始化对象

如果发生 1 → 3 → 2 重排

线程A执行到步骤2时,线程B可能拿到一个“未初始化完成的对象”。


如何保证有序性?

volatile

  • 插入 内存屏障(Memory Barrier)
  • 禁止重排序

三者对比总结

特性 解决问题 关键点 典型实现
原子性 操作不可被打断 整体执行 synchronized / CAS
可见性 修改能被其他线程看到 主内存同步 volatile
有序性 禁止重排序 内存屏障 volatile

面试高频连问

1. volatile 保证原子性吗?

❌ 不保证
只保证 可见性 + 有序性


2. synchronized 保证什么?

✅ 原子性
✅ 可见性
✅ 有序性


3. volatile 和 synchronized 的本质区别?

volatile synchronized
轻量级 重量级
不加锁 加锁
不能保证复合操作原子性 可以

一句话理解

  • 原子性:不能被“打断”
  • 可见性:不能被“看不到”
  • 有序性:不能被“乱序”

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐