Java volatile案例

wen java案例 3

本文目录导读:

Java volatile案例

  1. 经典案例:线程可见性问题
  2. 双重检查锁(Double-Checked Locking)场景
  3. 计数器案例(展示volatile不保证原子性)
  4. volatile应用场景总结
  5. volatile的三大特性总结
  6. 完整的生产案例
  7. 面试必问总结

下面我来详细讲解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() 可以分解为以下三个步骤:

  1. 分配内存空间
  2. 初始化对象
  3. 将内存地址赋值给引用

编译器可能将其重排序为:

  1. 分配内存空间
  2. 将内存地址赋值给引用(未初始化)
  3. 初始化对象

如果线程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++ 有三个步骤:

  1. 读取count值
  2. 加1
  3. 写回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?

  1. 状态标志:简单的开关状态
  2. 单次发布:只读状态的安全发布
  3. 独立观察:多个变量相互独立,不涉及复合操作

什么时候不该用volatile?

  1. 需要保证复合操作的原子性(如 count++
  2. 操作依赖当前值(如 if(x) { ... }
  3. 需要互斥访问

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屏障
    }
}

核心要点:

  1. 可见性:volatile变量的修改立即对其他线程可见(通过主内存)
  2. 有序性:禁止指令重排序(通过内存屏障)
  3. 不保证原子性:复合操作需要另外加锁或使用原子类
  4. 典型应用:双重检查锁、状态标志、安全发布不可变对象

性能影响:

  • volatile读操作开销与普通读相近
  • volatile写操作开销比普通写大(需要刷新到主存和使其他缓存失效)
  • 但比synchronized的性能开销小得多(无锁操作)

抱歉,评论功能暂时关闭!