Volatile 能保证原子性吗?为什么?

Volatile 不能保证原子性。

volatile 关键字在 Java 中主要解决 可见性有序性 问题:

  • 可见性:对 volatile 变量的写操作会立即刷新到主内存,且读操作会直接从主内存读取最新值,确保其他线程能立即看到修改。

  • 有序性:禁止编译器和处理器对 volatile 变量的读写操作进行重排序。

但它 不提供原子性保证

volatile 适合单一写线程、多读线程的场景(如状态标志位),不适合需要复合操作的场景。

如何保证原子性?

  • 使用 synchronizedLock

  • 使用原子类(如 AtomicInteger

Synchronized 是如何保证原子性、可见性、有序性的?

原子性

问题:多线程并发时,操作可能被其他线程中断,导致中间状态被覆盖。
synchronized 的解决方案

  1. 互斥锁机制:通过 monitorentermonitorexit 指令(对应 Monitor 的获取和释放),确保同一时刻只有一个线程能进入同步代码块。

  2. 临界区保护:被 synchronized 修饰的代码块(或方法)成为临界区,线程必须持有锁才能执行临界区代码。

  3. 原子操作保障:锁的排他性保证了临界区内的操作不会被线程切换打断,从而实现了原子性。

可见性

问题:线程修改共享变量后,其他线程可能因本地缓存(工作内存)未更新而读到旧值。
synchronized 的解决方案

  1. 内存屏障

    • monitorenter(获取锁)时,会插入 Load Barrier,强制从主内存重新加载变量。

    • monitorexit(释放锁)时,会插入 Store Barrier,强制将工作内存的修改刷新到主内存。

  2. Happens-Before 规则

    • 根据 JMM 规范,对一个锁的解锁操作 Happens-Before 后续对这个锁的加锁操作。

    • 前一个线程在释放锁前的所有修改,对后续获取锁的线程可见。

有序性

问题:编译器和处理器可能对指令重排序,导致代码执行顺序与预期不一致。
synchronized 的解决方案

  1. 锁的内存语义

    • 进入 synchronized 块时(获取锁),相当于执行 Acquire 操作,禁止后续读写操作重排序到锁获取之前。

    • 退出 synchronized 块时(释放锁),相当于执行 Release 操作,禁止前面的读写操作重排序到锁释放之后。

  2. 单线程语义:在同步块内部,JVM 会遵守 as-if-serial 语义,即单线程执行结果不会被重排序破坏,但允许不影响结果的重排序。

总结

特征

实现机制

原子性

通过 Monitor 的互斥锁,保证临界区代码不可中断。

可见性

通过内存屏障强制刷新主内存,结合 Happens-Before 规则保证修改可见。

有序性

通过 Acquire-Release 内存屏障限制指令重排序,遵守单线程 as-if-serial 语义。