Java 中的 volatile 关键字

volatile 是 Java 中一个非常重要的关键字,用于确保多线程环境中的共享变量的可见性。它确保了当一个线程修改某个变量的值时,其他线程可以立即看到这个修改,而不是使用线程本地的缓存。

volatile 关键字的作用

在多线程编程中,线程会在本地缓存中存储变量的副本,这意味着某个线程修改了变量的值,另一个线程可能并不会立即看到这些变化。为了保证变量的变化能够立即对其他线程可见,我们使用 volatile

volatile 的核心作用:

  1. 保证可见性:当一个线程修改了 volatile 变量的值,其他线程会立即看到这个修改。
  2. 禁止指令重排序:使用 volatile 修饰的变量会在访问时避免一些优化,比如 JVM 或 CPU 的指令重排序,从而保持代码执行的顺序性。

volatile 的行为

  1. 可见性:在多线程环境中,volatile 确保当一个线程修改了变量的值,其他线程能够立即看到这个变化。
  2. 禁止指令重排序volatile 变量在编译和运行时会强制执行有序的操作,禁止对其进行指令重排序优化。

如何使用 volatile

volatile 只能用于变量,而不能用于方法或类。它的作用是保证变量在多个线程中保持一致性。

示例 1:volatile 变量保证可见性

public class VolatileExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        // 线程1:修改 flag
        new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
            System.out.println("线程1 修改 flag 为 true");
        }).start();

        // 线程2:读取 flag
        new Thread(() -> {
            while (!flag) {
                // 当 flag 为 false 时,持续检查
            }
            System.out.println("线程2 检测到 flag 为 true,退出");
        }).start();
    }
}

解释:

  • flag 被声明为 volatile,确保当线程 1 修改 flag 值为 true 时,线程 2 会立即看到这个变化并退出循环。
  • 如果没有 volatile,线程 2 可能由于使用线程本地的缓存,会永远看不到 flag 的变化,导致死循环。

volatile 的工作原理

  • 可见性:当一个线程更新了 volatile 变量的值时,这个值会立即刷新到主内存中,其他线程在访问这个变量时会从主内存获取最新的值。
  • 禁止缓存:通常,线程会将共享变量缓存到线程本地的缓存中,从而减少访问主内存的开销。但在 volatile 变量上,Java 保证所有线程直接访问主内存,不使用缓存。
  • 禁止重排序:编译器和 JVM 在优化时,会尽量重排指令以提高效率。volatile 保证指令的执行顺序不会被改变,确保操作的顺序性。

⚠️ volatile 的限制

尽管 volatile 解决了可见性问题,但它并不适用于解决所有的并发问题。特别是它无法保证原子性,即 volatile 变量不能用于替代锁来进行复杂的原子操作。

不适合的场景

  1. 复合操作:如果对 volatile 变量执行多个步骤的复合操作(如自增 i++),无法保证原子性。例如:
    volatile int i = 0;
    i++;  // 非原子操作,i++ 分为读取 i、加 1、再写回 i,多个线程操作时会产生竞态条件
    
  2. 不保证原子性volatile 并不会像 synchronizedReentrantLock 一样,确保对变量的操作是原子的。它只是保证可见性和禁止重排序。

volatile 使用场景

  1. 标志位:通常用于实现线程间的控制标志,指示线程是否退出、是否继续执行等。
  2. 单例模式volatile 可以用来优化单例模式,确保单例对象被正确地初始化。
  3. 控制并发执行:用于多个线程之间的协调,确保一个线程的修改对其他线程可见。

示例 2:单例模式中的 volatile

在双重检查锁定的单例模式中,volatile 可以确保 instance 的正确创建。

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();  // 通过 volatile 保证 instance 的可见性
                }
            }
        }
        return instance;
    }
}

解释:

  • 这里 instance 被声明为 volatile,这可以防止因为 JVM 或 CPU 的指令重排而导致单例模式失效。
  • volatile 保证了其他线程在第一次初始化 instance 时会看到它已经被正确地初始化。

总结

特性 说明
可见性 volatile 确保多线程中变量的修改对其他线程立即可见。
禁止重排序 volatile 防止 JVM 或 CPU 重排变量的读写指令。
原子性 volatile 不保证复合操作的原子性,如 i++
使用场景 适用于控制标志、单例模式等需要保证可见性的场景。
限制 不能用于保证复合操作的原子性。

你可能感兴趣的:(java,开发语言)