本文目录导读:

- 经典案例:线程可见性问题
- 双重检查锁(Double-Checked Locking)场景
- 计数器案例(展示volatile不保证原子性)
- volatile应用场景总结
- volatile的三大特性总结
- 完整的生产案例
- 面试必问总结
下面我来详细讲解Java中volatile关键字的经典案例和原理。
经典案例:线程可见性问题
错误示例(没有volatile)
public class NoVolatileExample {
// 没有volatile,线程间不可见
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread reader = new Thread(() -> {
System.out.println("Reader线程开始执行...");
while (!flag) {
// 空循环等待flag变为true
}
System.out.println("Reader线程检测到flag变化,退出循环");
});
Thread writer = new Thread(() -> {
System.out.println("Writer线程开始执行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("Writer线程已将flag设置为true");
});
reader.start();
writer.start();
reader.join();
writer.join();
System.out.println("程序执行完毕");
}
}
运行结果:Reader线程可能会永远停不下来,因为flag的变化对Reader线程不可见(JIT编译优化)。
正确示例(使用volatile)
public class VolatileExample {
// 使用volatile保证可见性
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread reader = new Thread(() -> {
System.out.println("Reader线程开始执行...");
while (!flag) {
// 空循环等待
}
System.out.println("Reader线程检测到flag变化,退出循环");
});
Thread writer = new Thread(() -> {
System.out.println("Writer线程开始执行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("Writer线程已将flag设置为true");
});
reader.start();
writer.start();
reader.join();
writer.join();
System.out.println("程序执行完毕");
}
}
运行结果:Reader线程能及时感知到flag的变化,正常退出。
双重检查锁(Double-Checked Locking)场景
public class Singleton {
// volatile防止指令重排序
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 创建实例
}
}
}
return instance;
}
}
为什么需要volatile?
new Singleton() 可以分解为以下三个步骤:
- 分配内存空间
- 初始化对象
- 将内存地址赋值给引用
编译器可能将其重排序为:
- 分配内存空间
- 将内存地址赋值给引用(未初始化)
- 初始化对象
如果线程A执行了步骤1和2(未初始化),线程B进来看到instance != null,直接返回了未初始化的对象,导致错误。
用volatile可以禁止指令重排序,确保先初始化再赋值。
计数器案例(展示volatile不保证原子性)
public class VolatileCounter {
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
// 创建100个线程同时增加计数
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 不是原子操作!
}
}).start();
}
Thread.sleep(3000);
// 期望结果是100000,但实际可能不是
System.out.println("最终count值: " + count);
}
}
分析:count++ 有三个步骤:
- 读取count值
- 加1
- 写回count
volatile只保证可见性,不保证原子性,所以最终结果可能小于100000。
解决方案(使用synchronized或原子类)
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count.incrementAndGet(); // 原子操作
}
}).start();
}
Thread.sleep(3000);
System.out.println("最终count值: " + count.get()); // 输出100000
}
}
volatile应用场景总结
适用场景
public class VolatileUseCase {
// 场景1:状态标志位
private volatile boolean shutdown = false;
// 场景2:单次状态发布
private volatile boolean initialized = false;
// 场景3:配置值更新
private volatile String configValue = "default";
public void shutdown() {
shutdown = true; // 写入可见性
}
public void doWork() {
while (!shutdown) {
// 执行工作
}
}
// 场景4:安全发布不可变对象(配合final)
private static class ImmutableObject {
private final int value;
private final String name;
ImmutableObject(int value, String name) {
this.value = value;
this.name = name;
}
}
private volatile ImmutableObject snapshot;
public void updateSnapshot(int value, String name) {
snapshot = new ImmutableObject(value, name); // 安全发布
}
public ImmutableObject getSnapshot() {
return snapshot;
}
}
volatile的三大特性总结
public class VolatileSummary {
// 1. 可见性:volatile变量的修改对其他线程立即可见
private volatile boolean visibleFlag;
// 2. 有序性:禁止指令重排序(添加内存屏障)
private volatile int orderValue;
// 3. volatile不保证原子性(不具备原子性)
private volatile int notAtomic = 0;
public void increment() {
notAtomic++; // 非原子操作
}
}
内存屏障详解
public class MemoryBarrier {
private int a;
private volatile boolean flag = false;
public void write() {
a = 1; // 普通写
flag = true; // volatile写,插入StoreStore屏障
// 确保a=1在flag=true之前完成
}
public void read() {
if (flag) { // volatile读,插入LoadLoad屏障
int temp = a; // 确保读取到最新的a值
}
}
}
完整的生产案例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VolatileCompleteExample {
// 服务状态
private volatile boolean running = true;
// 当前任务数(用AtomicInteger保证原子性)
private final java.util.concurrent.atomic.AtomicInteger taskCount =
new java.util.concurrent.atomic.AtomicInteger(0);
// 配置信息(用volatile保证可见性)
private volatile TaskConfig config = new TaskConfig(5, "默认配置");
// 不可变配置对象
public static class TaskConfig {
private final int poolSize;
private final String name;
public TaskConfig(int poolSize, String name) {
this.poolSize = poolSize;
this.name = name;
}
public int getPoolSize() { return poolSize; }
public String getName() { return name; }
}
// 执行任务
public void process() {
ExecutorService executor = Executors.newFixedThreadPool(config.getPoolSize());
while (running && !Thread.currentThread().isInterrupted()) {
try {
taskCount.incrementAndGet();
executor.submit(() -> {
try {
System.out.println(Thread.currentThread().getName() +
" 执行任务: " + config.getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
taskCount.decrementAndGet();
}
});
Thread.sleep(200);
} catch (InterruptedException e) {
running = false;
Thread.currentThread().interrupt();
}
}
executor.shutdown();
System.out.println("服务停止,剩余任务数: " + taskCount.get());
}
// 停止服务
public void stop() {
running = false; // volatile可见性,立即通知其他线程停止
}
// 更新配置
public void updateConfig(int poolSize, String name) {
if (poolSize <= 0 || name == null || name.isEmpty()) {
throw new IllegalArgumentException("无效的配置参数");
}
config = new TaskConfig(poolSize, name); // volatile安全发布
System.out.println("配置已更新: " + config.getName());
}
public static void main(String[] args) throws InterruptedException {
VolatileCompleteExample example = new VolatileCompleteExample();
// 启动服务线程
Thread serviceThread = new Thread(example::process);
serviceThread.start();
// 模拟运行5秒后停止
Thread.sleep(5000);
example.updateConfig(10, "新配置");
Thread.sleep(2000);
example.stop();
serviceThread.join();
System.out.println("主程序执行完毕");
}
}
面试必问总结
volatile vs synchronized vs Atomic
| 特性 | volatile | synchronized | Atomic类 |
|---|---|---|---|
| 可见性 | |||
| 原子性 | ✅(单变量) | ||
| 有序性 | ✅(禁止重排序) | ||
| 性能 | 最高 | 较低(有锁) | 较高(CAS) |
| 使用场景 | 状态标志、单次发布 | 复合操作、临界区 | 计数器、累加器 |
什么时候该用volatile?
- 状态标志:简单的开关状态
- 单次发布:只读状态的安全发布
- 独立观察:多个变量相互独立,不涉及复合操作
什么时候不该用volatile?
- 需要保证复合操作的原子性(如
count++) - 操作依赖当前值(如
if(x) { ... }) - 需要互斥访问
volatile的实现原理:
// 伪代码展示volatile的内存屏障插入
public class MemoryBarrierExample {
int a = 1;
volatile boolean flag = false;
void write() {
a = 2; // 普通写
// 插入StoreStore屏障
flag = true; // volatile写
// 插入StoreLoad屏障
}
void read() {
// 插入LoadLoad屏障
if (flag) { // volatile读
int b = a; // 看到最最新的a值
}
// 插入LoadStore屏障
}
}
核心要点:
- 可见性:volatile变量的修改立即对其他线程可见(通过主内存)
- 有序性:禁止指令重排序(通过内存屏障)
- 不保证原子性:复合操作需要另外加锁或使用原子类
- 典型应用:双重检查锁、状态标志、安全发布不可变对象
性能影响:
- volatile读操作开销与普通读相近
- volatile写操作开销比普通写大(需要刷新到主存和使其他缓存失效)
- 但比synchronized的性能开销小得多(无锁操作)