volatile关键字介绍-Java

volatile 是 Java 中用于修饰变量的关键字,常用于多线程编程。它是一个轻量级的同步机制,用来确保共享变量在多个线程之间的可见性,并防止指令重排序。

1. volatile 的特性

volatile 修饰的变量具备以下两个关键特性:

1.可见性(Visibility):

  • 当一个线程修改了 volatile 修饰的变量,修改后的值会立刻被刷新到主内存。
  • 其他线程读取这个变量时,会直接从主内存中读取最新值,而不是从线程的工作内存中读取旧值。效果:所有线程对 volatile 变量的访问是最新的。

2.禁止指令重排序(Orderliness):

  • 在 volatile 变量的读写操作前后,编译器和处理器不会对代码进行重排序。
  • 效果:确保对 volatile 变量的操作顺序符合程序的逻辑预期。

2. volatile 的实现原理

(1) 内存可见性原理

  • Java 内存模型(JMM)
  • Java 使用主内存(Main Memory)和线程工作内存(Working Memory)来管理共享变量。
  • 通常,线程会将共享变量从主内存拷贝到自己的工作内存中,进行操作后再写回主内存。
  • volatile 保证变量的修改会立即刷新到主内存,同时禁止线程从工作内存中读取过期数据。

(2) 指令重排序规则

  • 编译器和 CPU 在执行程序时,可能会出于性能优化对指令进行重排序。
  • 使用 volatile 修饰变量后,读写操作会添加内存屏障(Memory Barrier),防止指令重排序。
  • 内存屏障是一种 CPU 指令,确保屏障前的所有操作在屏障之后的操作之前完成。

3. volatile 的使用场景

(1) 状态标志变量

volatile 常用于表示线程间的状态,例如停止标志。

public class VolatileFlag {
    private volatile boolean running = true;

    public void stop() {
        running = false; // 修改 running 状态
    }

    public void execute() {
        while (running) {
            // 执行任务
        }
    }
}
  • 这里的 volatile 保证了 running 对所有线程的可见性。

(2) 单例模式的双重检查锁

volatile 可用于确保双重检查锁的线程安全。

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 这里的 volatile 防止实例化过程中指令重排序导致的线程安全问题。

(3) 配合非阻塞算法volatile 可以在某些非阻塞算法中保证共享变量的可见性。例如,计数器递增:

public class Counter {
    private volatile int count = 0;

    public void increment() {
        count++;
    }
}

注意count++ 不是原子操作,此处仅展示可见性。如果需要原子性,需使用 AtomicInteger

4. volatile 的局限性

  1. 不保证原子性:volatile 只能保证变量的可见性,不能保证操作的原子性。
  2. 仅适用于单个变量:volatile 只能保证单个变量的可见性,对于复合操作(如 i++)或多个变量的操作不提供安全性。
  3. 性能开销:使用 volatile 会降低一定的性能,因为它需要频繁地刷新主内存,导致缓存失效。

5. volatile 与 synchronized 的对比

特性

volatile

synchronized

功能

仅保证可见性,不能保证原子性

同时保证可见性和原子性

锁机制

无锁机制,适合轻量级操作

内置锁,适合复杂的线程同步

性能

性能较高,适合简单场景

性能较低(JDK 1.6 以后优化性能较好)

适用场景

状态标志、双重检查锁

复杂的线程同步场景

是否阻塞线程

非阻塞

阻塞线程

6. volatile 与 Atomic 的对比

特性

volatile

Atomic 类

功能

仅保证可见性,不能保证原子性

保证可见性和原子性

实现原理

使用内存屏障(Memory Barrier)

使用 CAS(Compare-And-Swap)

适用场景

状态标志、简单读写

计数器、复合操作

7. 关键点总结

  • 何时使用 volatile
  • 当变量需要被多个线程共享,并且是单个变量的简单状态标志时(如停止标志、状态标识)。
  • 不涉及复合操作(如 x++ 或 x = x + 1)。
  • 何时不适用 volatile
  • 如果需要保证操作的原子性(如计数器递增),则 volatile 不适合。
  • 涉及多个变量之间的同步或复合操作时,volatile 无法解决问题,需要使用 synchronized 或其他并发工具。
Java碎碎念 文章被收录于专栏

来一杯咖啡,聊聊Java的碎碎念呀

全部评论

相关推荐

评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务