java 中 volatile 修饰的 long 和 double 读写是原子的。在 java 内存模型(jmm)中,除了 long 和 double 之外的所有其他类型(如 int、boolean、引用等)的读写操作都是原子的,而 long 和 double 属于 64 位数据类型,在 32 位 jvm 上默认可能被拆分为两次 32 位的读写,但 jmm 规范强制要求对 volatile 类型的 long 和 double 进行原子性访问,即要么完整读取 64 位,要么完整写入 64 位,不会出现读到“半个值”的情况。

为什么 volatile long double 需要特殊处理?
因为 long 和 double 占用 64 位,而在 32 位系统上,CPU 一次只能处理 32 位数据,所以非 volatile 的 long/double 读写可能不是原子的。但一旦加上 volatile,JVM 必须保证原子性,通常通过汇编层面的锁指令或内存屏障来实现。

public class VolatileLongDemo {
private volatile long value = 0;
public void set(long v) { value = v; }
public long get() { return value; }
public static void main(String[] args) {
VolatileLongDemo demo = new VolatileLongDemo();
Thread t1 = new Thread(() -> demo.set(1L));
Thread t2 = new Thread(() -> System.out.println(demo.get()));
t1.start();
t2.start();
}
}
上述代码中,value 是 volatile long,即使运行在 32 位 JVM 上,set() 和 get() 也是原子操作,不会发生数据撕裂。
volatile 如何保证可见性和有序性?
volatile 不仅保证原子性(针对 long/double),还保证可见性和有序性。可见性意味着一个线程修改了 volatile 变量,其他线程能立即看到最新值;有序性意味着禁止指令重排序。这些特性通过内存屏障实现,例如写屏障和读屏障。

public class VolatileVisibilityDemo {
private volatile boolean flag = false;
private int data = 0;
public void writer() {
data = 42; // 非 volatile 变量
flag = true; // volatile 变量,触发写屏障
}
public void reader() {
if (flag) { // volatile 读,触发读屏障
System.out.println(data); // 保证看到 data=42
}
}
}
在 reader() 中,如果 flag 为 true,则 data 的值一定是 42,因为 volatile 写屏障确保 data=42 在 flag=true 之前完成,且读屏障确保 data 的读取能看到之前的写入。
volatile 不能保证复合操作的原子性
虽然 volatile long 的单个读写是原子的,但像 i++ 这样的复合操作(读-改-写)不是原子的。例如,多个线程同时执行 count++ 会导致竞态条件,因为该操作包含三个步骤:读取当前值、加 1、写回新值。如果需要原子复合操作,应使用 AtomicLong 或同步块。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











