掘金 后端 ( ) • 2024-04-03 10:51

是什么

是Java中的一种轻量级的同步机制,提供了内存可见性和防止指令重排序的特性(可见性,有序性,但不能保证原子性,如不能保证i++的原子性):
内存可见性:在多线程环境下,为了提高性能,每个线程可能会在自己的工作内存中保持主内存的副本。如果一个变量不是用 volatile 声明的,那么可能一个线程在更新这个变量的值时,其它线程看到的还是旧值,因为更新后的值还没有被写回主内存,或者其它线程的工作内存中的副本还没有被更新。当一个变量被声明为 volatile 后,对这个变量的写操作会立即同步到主内存中,对这个变量的读操作会从主内存中读取,这就保证了变量在所有线程中的内存可见性。
防止指令重排序:指令重排序是编译器或处理器为了优化程序性能而进行的一种优化,它可能会改变程序指令的执行顺序。在某些情况下,指令重排序可能会破坏多线程程序的正确性。volatile 关键字可以防止对其标记的变量进行指令重排序,确保程序在并发环境下的正确性。

使用场景

  1. 状态标志

假设有两个线程:一个是主线程,用于执行主要任务;另一个是监控线程,用于监控某个条件并告诉主线程何时停止执行。我们可以使用volatile变量作为这个停止信号

public class VolatileFlagExample {
    private volatile boolean running = true;

    public void example() throws InterruptedException {
        Thread worker = new Thread(() -> {
            while (running) {
                // 执行任务
            }
            System.out.println("Worker stopped.");
        });

        worker.start();

        Thread.sleep(1000); // 模拟任务执行了一段时间后
        running = false; // 修改停止标志,worker线程将会停止执行
    }

    public static void main(String[] args) throws InterruptedException {
        new VolatileFlagExample().example();
    }
}

注意:没有volatile标记,也可能正常停止。其正确性可能会受到多种因素的影响,比如JVM优化、处理器架构以及Java内存模型的实现细节等。

  1. 双重检查锁定 实现单例模式

volatile 可以防止因为指令重排序造成的单例对象未被正确初始化。

public class SingletonExample {
    private static volatile SingletonExample instance;

    private SingletonExample() {}

    public static SingletonExample getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (SingletonExample.class) {
                if (instance == null) { // 第二次检查
                    instance = new SingletonExample(); // 实例化
                }
            }
        }
        return instance;
    }
}

优点 缺点

能保证可见性,有序性,但是保证不了原子性。

内部如何实现

volatile的内部实现细节依赖于Java虚拟机(JVM)和底层硬件平台,特别是处理器的内存模型。
保证可见性:直接在内存操作,禁止在线程内部缓存。这意味着一个线程对volatile变量的修改立即对其他线程可见,因为变量的值在每次访问时都会从主内存中读取,而每次修改时都会写回主内存。
保证有序性:在没有volatile修饰的变量上,编译器和处理器可能会对代码进行重排序,以优化性能。但是,对于volatile变量,JVM会插入必要的内存屏障(Memory Barriers)或内存栅栏指令来阻止指令重排序。内存屏障是一种CPU指令,用于实现对内存操作的顺序限制。

  • 写屏障(Write Barrier):在每次写入volatile变量后,会插入写屏障,确保对该变量的修改之前的所有操作都已经完成,并且对该变量的修改对其他处理器可见。
  • 读屏障(Read Barrier):在每次读取volatile变量前,会插入读屏障,确保对该变量的读取操作能看到其他处理器对这些volatile变量最新的写入。

名词概念补充

可见性:是指当一个线程修改了共享变量的值时,其他线程能否立即看到这一变化的特性。
有序性:有序性是指程序执行的顺序如何影响多线程程序的结果。