volatile保证可见性不保证原子

wen java案例 2

本文目录导读:

volatile保证可见性不保证原子

  1. volatile 保证了什么?
  2. volatile 不保证什么?
  3. 一个经典的反例:volatile 无法实现计数器
  4. 既然 volatile 不保证原子性,那怎么办?
  5. 总结对比

你理解得非常准确。volatile 保证可见性,但不保证原子性,这是 Java 并发编程中的一个核心概念。

为了帮你更透彻地理解,我把这两个特性拆开解释,并给出一个典型的“原子性失效”的例子。

volatile 保证了什么?

保证可见性

  • 含义:当一条线程修改了一个 volatile 变量的值,新值对于其他线程来说是立即可见的。
  • 原理:Java 内存模型(JMM)规定,对 volatile 变量的写操作,会强制将线程工作内存中的变量值刷新到主内存;而对 volatile 变量的读操作,会强制从主内存中读取最新值,这样就避免了线程间“看不见对方修改”的问题。
  • 场景:适合用于状态标记位boolean flag)。

volatile 不保证什么?

不保证原子性

  • 含义:对于 count++ 这种复合操作(读取-修改-写入),volatile 无法保证整个操作不被其他线程打断。

  • 本质原因count++ 在字节码层面至少是三个指令:

    1. getfield (读取 count 的值到栈顶)
    2. iadd (将 count 加 1)
    3. putfield (将新值写回 count)

    即使 countvolatile 的,线程 A 执行完 getfieldiadd 后,在即将执行 putfield 时,线程 B 可能已经修改了 count 的值,线程 A 最终写回的值是基于它之前读到的旧值,从而导致线程 B 的更新被覆盖,这就是典型的丢失更新问题。

一个经典的反例:volatile 无法实现计数器

下面这段代码,虽然 countvolatile 的,但最终结果几乎不可能是 20000(因为两个线程各累加 10000 次)。

public class VolatileAtomicDemo {
    // volatile 保证可见性,但不保证原子性
    private static volatile int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                count++;  // 这不是原子操作!
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                count++;  // 这不是原子操作!
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count 的最终结果:" + count);
        // 预期:20000,实际:通常是一个小于 20000 的随机数(如 19876, 19923 等)
    }
}

既然 volatile 不保证原子性,那怎么办?

  1. 使用 synchronized 关键字:加锁可以保证原子性(代码块内的操作不可分割)。

    public synchronized static void increment() {
        count++;
    }
  2. 使用 java.util.concurrent.atomic 包下的原子类AtomicInteger,它底层通过 CAS(Compare-And-Swap)机制保证了原子性,性能通常比 synchronized 好。

    private static AtomicInteger atomicCount = new AtomicInteger(0);
    // 使用时
    atomicCount.incrementAndGet();

总结对比

特性 volatile synchronized / AtomicInteger
可见性 保证 (强制刷新主内存) 保证 (释放锁时刷新主内存)
原子性 不保证 保证
适用场景 单一变量的读写(如状态标记 flag 需要复合操作(如 i++)、互斥访问
性能开销 较低 (无锁,但有内存屏障) 较高 (锁竞争 / CAS 自旋)

一句话记住volatile 适合做开关boolean running = true),不适合做计数器int i++)。

抱歉,评论功能暂时关闭!