volatile关键字

volatile 关键字

volatile 修饰字段,建立线程间的可见性和特定有序性保证。它不提供互斥,不能自动把复合操作变成原子操作。

代码块JAVA · 1 行收起展开
private volatile boolean running = true;

它不能修饰局部变量、方法或类。

可见性

一个线程对 volatile 字段的写,happens-before 后续其他线程对该字段的读。因此读线程也能看到该次 volatile 写之前完成的普通写入。

代码块JAVA · 15 行收起展开
class Task {
    private int result;
    private volatile boolean ready;

    void produce() {
        result = 42;
        ready = true;
    }

    void consume() {
        if (ready) {
            System.out.println(result);
        }
    }
}

“刷新到主内存”只是近似说法,正式依据是 Java 内存模型的 happens-before 规则。

有序性

volatile 读写具有内存屏障语义,限制相关操作跨越 volatile 边界重排。它不是禁止所有重排,而是保证其他线程不能观察到违反内存模型的顺序。

不保证复合操作原子性

代码块JAVA · 5 行收起展开
private volatile int count;

void increment() {
    count++; // 读、加、写,不是原子操作
}

多个线程仍会丢失更新。应使用 AtomicInteger 或 synchronized:

代码块JAVA · 5 行收起展开
private final AtomicInteger count = new AtomicInteger();

void increment() {
    count.incrementAndGet();
}

典型场景

停止标志

代码块JAVA · 11 行收起展开
private volatile boolean stopped;

void runLoop() {
    while (!stopped) {
        doOneUnitOfWork();
    }
}

void stop() {
    stopped = true;
}

若线程阻塞在 I/O 或等待中,仅修改标志不能唤醒它,还需要中断等机制。

发布不可变快照

代码块JAVA · 5 行收起展开
private volatile Config config = Config.defaults();

void reload() {
    config = loadNewImmutableConfig();
}

volatile List 只保证列表引用可见,不会让列表内部修改自动线程安全。

双重检查锁

代码块JAVA · 17 行收起展开
class Singleton {
    private static volatile Singleton instance;

    static Singleton getInstance() {
        Singleton result = instance;
        if (result == null) {
            synchronized (Singleton.class) {
                result = instance;
                if (result == null) {
                    result = new Singleton();
                    instance = result;
                }
            }
        }
        return result;
    }
}

volatile 防止对象引用在初始化效果完整可见之前被其他线程观察。实际单例通常优先使用枚举或静态内部类持有者模式。

选择原则

只有当写操作不依赖旧值,且字段不参与必须整体一致的多变量不变量时,才适合 volatile。否则使用锁、原子类或并发容器。

延伸阅读