在 Java 中,volatile
关键字的作用主要有以下两点:
1. 保证变量的可见性
当一个变量被
volatile
修饰时,它的值会直接存储在主内存中,而不是仅存在线程的工作内存(CPU 缓存)中。这样,当一个线程修改
volatile
变量的值时,其他线程立即能够看到最新的值,而不会读取过期的数据。
2. 禁止指令重排序
现代 CPU 和 JIT 编译器为了优化性能,可能会对代码指令进行重排序,但
volatile
变量的读/写操作不会被重排序到其他非volatile
变量的读/写操作之前或之后。这可以在某些情况下保证有序性,比如在双重检查锁单例模式(DCL)中,防止未初始化对象被错误读取。
⚠️ 但是 volatile
不能保证原子性
volatile
只保证了可见性和有序性,但不能保证对变量的操作是原子性的。例如,volatile
不能保证 count++
操作的线程安全,因为 count++
其实是三步操作:
读取
count
的值计算
count + 1
将新值写回
count
这三步不是一个原子操作,可能会导致线程安全问题。
如果要保证原子性,可以使用 synchronized
、AtomicInteger
或 Lock
代替 volatile
。
✅ 使用 volatile
的典型场景:
多线程状态标记(比如
boolean flag
)单例模式(DCL 方式)(
private static volatile Singleton instance
)Double-Checked Locking
机制,防止指令重排序导致的对象未完全初始化问题
示例代码:
1️⃣ 适合使用 volatile
:线程间的可见性
class VolatileExample {
private static volatile boolean flag = false;
public static void main(String[] args) {
new Thread(() -> {
while (!flag) {
// 等待 flag 变为 true
}
System.out.println("线程 1 结束");
}).start();
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
new Thread(() -> {
flag = true; // 修改 flag,另一个线程会立刻感知到
System.out.println("线程 2 修改了 flag");
}).start();
}
}
2️⃣ 不适合使用 volatile
:不能保证原子性
class VolatileAtomicityTest {
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
count++; // 不是原子操作,可能丢失更新
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终 count 值:" + count); // 结果可能小于 20000
}
}
✅ 解决方法:使用 AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
class AtomicExample {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet(); // 线程安全的自增
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终 count 值:" + count); // 正确输出 20000
}
}
总结:
如果只是保证可见性,可以用 volatile
,但如果涉及复合操作(如++),要用 synchronized
或 Atomic
类。