【java】volatile

17 阅读3分钟

volatile 是 Java 中最轻量级的同步机制,它通过 内存屏障 和 禁止指令重排序 来保证变量的可见性和有序性。

1. 核心特性

volatile 实现了两大关键特性:

  1. 可见性保证:一个线程对 volatile 变量的修改能立即被其他线程看到
  2. 禁止指令重排序:防止编译器和处理器对 volatile 变量相关指令进行重排序

2. 底层实现机制

2.1 内存屏障 (Memory Barrier)

JVM 通过 插入特定类型的内存屏障 来实现 volatile 语义:

屏障类型作用
LoadLoad确保本屏障前的读操作先于屏障后的读操作完成
StoreStore确保本屏障前的写操作先于屏障后的写操作完成
LoadStore确保本屏障前的读操作先于屏障后的写操作完成
StoreLoad确保本屏障前的所有写操作对其他处理器可见后才执行屏障后的读操作

对于 volatile 变量的访问:

  • volatile 读:相当于在读取后插入 LoadLoad + LoadStore 屏障
  • volatile 写:相当于在写入前插入 StoreStore 屏障,写入后插入 StoreLoad 屏障

2.2 具体实现(以 x86 架构为例)

x86 处理器本身已经实现了较强的内存一致性模型,所以:

  • volatile 写操作编译为带有 lock 前缀的指令(如 lock addl $0,0(%rsp)

  • lock 指令会:

    • 将当前处理器缓存行写回内存
    • 使其他处理器的对应缓存行失效
    • 相当于执行了 StoreLoad 屏障

3. 从 Java 代码到机器指令

示例代码:

public class VolatileExample {
    private volatile int counter = 0;
    
    public void increment() {
        counter++;  // 不是原子操作!
    }
    
    public int get() {
        return counter;
    }
}

3.1 volatile 写操作编译结果

counter++ 实际会编译为:

  1. 读取 volatile 变量(带屏障)
  2. 执行加1操作
  3. 写入 volatile 变量(带屏障)

对应的伪汇编代码:

; volatile 读
mov eax, [counter]  ; 从内存加载
; LoadLoad + LoadStore 屏障

; 加1操作
add eax, 1

; volatile 写
mov [counter], eax  ; 写入内存
lock addl $0,0(%rsp) ; StoreLoad 屏障

3.2 volatile 读操作编译结果

return counter; 会编译为:

mov eax, [counter]  ; 从内存加载
; LoadLoad + LoadStore 屏障

4. 与 happens-before 关系

根据 JMM (Java Memory Model):

  • volatile 写 happens-before 任何后续的 volatile 读
  • 线程 A 写入 volatile 变量 → 线程 B 读取该变量,能保证看到线程 A 的所有写操作(包括非 volatile 变量)

5. 典型使用场景

  1. 状态标志位

    volatile boolean shutdownRequested;
    
    public void shutdown() {
        shutdownRequested = true;
    }
    
    public void doWork() {
        while(!shutdownRequested) {
            // 执行任务
        }
    }
    
  2. 单例模式(双重检查锁定)

    class Singleton {
        private static volatile Singleton instance;
        
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized(Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    • volatile 防止对象初始化时的指令重排序

6. 局限性

  • 不保证原子性:复合操作(如 i++)仍需同步
  • 性能影响:volatile 写比普通写慢约 50-100 个时钟周期
  • 不适用复杂同步:无法实现条件等待等复杂同步需求

7. 与 synchronized 对比

特性volatilesynchronized
原子性仅保证单个读/写的原子性保证代码块原子性
可见性直接保证通过解锁-加锁保证
有序性限制重排序限制重排序
阻塞不会导致阻塞会导致阻塞
适用场景状态标志、一次性发布等简单场景需要互斥的复杂场景

volatile 的实现充分体现了 Java 内存模型的设计哲学:在保证正确性的前提下,允许编译器/处理器进行最大限度的优化。理解其原理有助于正确使用这一特性,避免常见的并发编程错误。