volatile关键字的作用是什么?它能保证原子性吗?

volatile 是 Java 中一个轻量级的同步关键字,主要用于修饰变量。它的核心作用是保证可见性禁止指令重排序 ,但不能保证原子性

一、volatile 的两大作用

1. 保证可见性

  • 问题背景:在 Java 内存模型(JMM)中,每个线程有自己的工作内存(缓存),主内存中的变量可能被线程缓存到本地。当一个线程修改了共享变量后,其他线程无法立刻看到这个修改,导致数据不一致。
  • volatile 解决方案
    volatile 修饰的变量,写操作 会立即刷新到主内存;读操作 会直接从主内存读取(跳过本地缓存)。
    这样,一个线程修改了 volatile 变量,其他线程能马上看到最新值。
java 复制代码
// 示例:volatile 保证可见性,用于线程停止标志
class Runner {
    private volatile boolean running = true;  // 不加 volatile,子线程可能永远看不到修改

    public void stop() { running = false; }

    public void run() {
        while (running) {
            // 执行任务
        }
    }
}

2. 禁止指令重排序(保证有序性)

  • 问题背景:JVM 和 CPU 为了优化性能,可能会对指令进行重排序(在不改变单线程执行结果的前提下)。在多线程环境下,重排序可能导致意想不到的错误。
  • volatile 解决方案
    volatile 变量的读写操作前后插入内存屏障(Memory Barrier),禁止编译器 / CPU 对相关指令重新排序。
    典型应用:双重检查锁定(Double-Checked Locking)单例模式
java 复制代码
// 错误的单例(不加 volatile,可能返回未完全初始化的对象)
public class Singleton {
    private static volatile Singleton instance;  // 必须 volatile
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(); // 实际分三步:1.分配内存 2.初始化 3.引用指向内存
                }   // volatile 禁止第 2 和 3 步的重排序,防止其他线程看到空引用但未初始化的对象
            }
        }
        return instance;
    }
}

二、volatile 不能保证原子性

为什么不能保证原子性?

原子性是指一个或多个操作要么全部执行且不被中断,要么全不执行。
volatile 只能保证单个读 / 写操作 的原子性(例如读一个 long 或写一个 long 是原子的),但复合操作 (如 i++)不是原子的:
i++ 包含"读 - 改 - 写"三步,在多线程下即使 ivolatile,也会发生丢失更新的问题。

java 复制代码
volatile int count = 0;

// 两个线程各执行 10000 次 count++,最终结果可能小于 20000
void increment() {
    count++;  // 1.读 count 到寄存器 2.加1 3.写回内存 ------ 不是原子操作
}

如何实现原子操作?

  • 使用 synchronizedReentrantLock 对整个复合操作加锁。
  • 使用 java.util.concurrent.atomic 包下的原子类,如 AtomicInteger(基于 CAS 实现,保证原子性)。
java 复制代码
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet();  // 原子自增

三、volatilesynchronized 对比

特性 volatile synchronized
作用 保证可见性、有序性(禁止重排序) 保证原子性、可见性、有序性(通过锁的互斥)
是否阻塞 不阻塞线程 阻塞其他竞争锁的线程
适用对象 只能修饰变量 修饰方法、代码块
原子性保证 不能保证复合操作的原子性 保证代码块内的操作原子执行
性能 比加锁轻量,读写开销小 较重(但现代 JVM 已优化)

四、典型使用场景

1. 状态标志(开关)

java 复制代码
volatile boolean shutdownRequested = false;
// 线程1: shutdownRequested = true;
// 线程2: while (!shutdownRequested) { ... }

2. 双重检查锁定的单例模式(如前述)

3. 读操作远多于写操作的"一写多读"变量

例如统计配置参数、温度传感器数值等,只有一个线程更新,多个线程读取,用 volatile 即可保证可见性,无需加锁。

总结

  • volatile 的作用 :保证变量在多线程间的可见性 以及禁止指令重排序
  • 不能保证原子性 :对于 ++-- 或其他复合操作,仍需加锁或使用原子类。
  • 选择建议 :如果只是需要某个变量的修改对其他线程立即可见,且操作本身是原子的(例如赋值 a = 1,或者 boolean 标志位),用 volatile;如果需要复合操作的原子性,用 synchronizedLock
相关推荐
麻瓜生活睁不开眼7 小时前
Android16修改全局桌面视图边框四直角显示为弧边圆角
android·java·深度学习
liang_jy7 小时前
文件管理(八)—— 虚拟文件系统
面试·操作系统
liang_jy8 小时前
文件管理(七)—— 文件系统的层次结构和布局
面试·操作系统
AFinalStone10 小时前
Android 7系统休眠唤醒(六)唤醒全链路
android·电源管理·休眠唤醒
雨白11 小时前
深入理解 Kotlin 协程 (九):互通有无,解构 Channel 缓冲策略与底层无锁机制
android·kotlin
孓最求完美12 小时前
Charles 进阶抓包教程:突破 SSL Pinning、代理检测与 Flutter 抓包限制
面试
灯塔@kuaidao12 小时前
平台交叉编译名词解释与基础流程
android
thesky12345613 小时前
智能体面试准备(九):工作流编排——DAG 与状态机两种范式的取舍与手写实现
面试·agent·状态机·工作流·智能体
__Witheart__13 小时前
3568 Android otg模式下adb热拔插不识别
android·adb·rockchip
Revolution6114 小时前
reactive 对象重新赋值后,表单为什么没有恢复默认值
前端·vue.js·面试