【JUC】volatile的使用
1. volatile 保证什么读写顺序?
假设有代码:
int a = 0;
volatile boolean flag = false;
// 线程 A
a = 1;
flag = true;
对于线程 A 来说,volatile 写 flag = 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 读,因此可以保证可见性和有序性,但不能保证复合操作的原子性。
更多推荐




所有评论(0)