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
相关推荐
flower_drop6 小时前
基于 WebUSB 与 CDP:在浏览器端实现 Android 设备通信与无证书抓包实践
android·chrome·测试工具·adb·bug·edge浏览器
Interview Aid1127 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
撩得Android一次心动8 小时前
Jetpack Compose 知识点整理1【个人用】
android·学习·kotlin·android jetpack·compose
ForteScarlet9 小时前
Kotlin 2.4.20 现已发布,新特性多不多?
android·java·开发语言·开源·kotlin·jetbrains
方白羽11 小时前
Android Studio 自动国际化插件:SmartI18n
android·android studio·intellij idea
ocean210312 小时前
2025-2026年AI提效与实践大厂面试高频问题
人工智能·面试·职场和发展·提示词工程·ai提效
程序员梅雨13 小时前
Linux & Shell 实用干货
linux·运维·服务器·后端·面试·php
码农coding14 小时前
android12 状态栏图标的加载流程
android
hai_android14 小时前
Kotlin 协程作用域源码剖析
android·kotlin
Yeyu15 小时前
Android 画中画(PiP)小窗播放:怎么做、以及它到底是怎么工作的
android