1. volatile 保证什么读写顺序?

假设有代码:

int a = 0;
volatile boolean flag = false;

// 线程 A
a = 1;
flag = true;

对于线程 A 来说,volatileflag = true 有一个重要规则:

在 volatile 写之前的普通写操作,不能被重排序到 volatile 写之后。

所以这里:

a = 1;
flag = true;

不能被重排序成:

flag = true;
a = 1;

否则其他线程看到 flag == true 时,可能还看不到 a = 1,这就出问题了。


再看线程 B:

// 线程 B
if (flag) {
    System.out.println(a);
}

这里读取 volatile 变量 flag 时,也有一个规则:

在 volatile 读之后的普通读操作,不能被重排序到 volatile 读之前。

所以:

if (flag) {
    System.out.println(a);
}

当线程 B 看到 flag == true 后,它一定能看到线程 A 在 flag = true 之前对共享变量的修改。

因此输出的 a 应该是:

1

2. 用一句话总结 volatile 的有序性

可以这么记:

volatile 写之前的操作,不允许重排到 volatile 写之后;volatile 读之后的操作,不允许重排到 volatile 读之前。

也可以理解为:

普通写
volatile 写

volatile 读
普通读

这两个顺序会被保护住。


3. volatile 不是保证所有代码都不重排序

例如:

int a = 1;
int b = 2;
volatile boolean flag = true;

volatile 主要关心的是普通变量和 volatile 变量之间的顺序。

它不代表 JVM 完全不做任何重排序。

比如前面的:

int a = 1;
int b = 2;

这两个普通写之间,如果没有依赖关系,理论上仍可能被重排序。

所以不能简单说:

volatile 可以保证所有读写顺序。

更准确的说法是:

volatile 可以通过内存屏障,禁止与 volatile 变量相关的特定指令重排序,从而保证一定的有序性。


4. volatile 和 happens-before

Java 内存模型中有一条规则:

对一个 volatile 变量的写操作,happens-before 后续对这个 volatile 变量的读操作。

例如:

// 线程 A
a = 1;
flag = true; // volatile 写

// 线程 B
if (flag) {  // volatile 读
    System.out.println(a);
}

因为:

flag = true

happens-before:

if (flag)

所以线程 A 中 flag = true 之前的修改,对线程 B 中 flag 之后的操作可见。


5. 但是 volatile 不保证原子性

比如:

volatile int count = 0;

count++;

这依然不是线程安全的。

因为 count++ 是三步:

读取 count
count + 1
写回 count

volatile 可以保证每次读到的是最新值,也能保证一定的顺序,但不能把这三步变成一个不可分割的原子操作。

所以:

volatile

适合做状态标记:

volatile boolean stop = false;

但不适合直接做并发自增:

volatile int count;
count++;

这种要用:

AtomicInteger

或者加锁。


最终总结

volatile 可以保证一定的读写顺序,但不是保证所有操作都严格按代码顺序执行。它通过内存屏障禁止与 volatile 变量相关的指令重排序:volatile 写之前的普通读写不能被重排到 volatile 写之后,volatile 读之后的普通读写不能被重排到 volatile 读之前。同时,volatile 写 happens-before 后续对同一变量的 volatile 读,因此可以保证可见性和有序性,但不能保证复合操作的原子性。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐