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
相关推荐
兰令水9 小时前
leecodecode【面试150】【2026.7.9打卡-java版本】
java·数据结构·leetcode·面试·职场和发展
星空语9 小时前
音频001_Android+Linux车载/手机音频全链路分层架构图
android·linux·音视频
CyL_Cly9 小时前
解决官网下载慢!Android Studio 最新版网盘下载
android·ide·android studio
智塑未来10 小时前
鸿蒙系统对比安卓、iOS,核心优势是什么?
android·ios·harmonyos
AI刀刀10 小时前
豆包智能体对话导出后,如何构建长期归档与高效检索体系?
android·人工智能·word·excel·ai导出鸭
用户9385156350711 小时前
Node.js 路径与文件系统:从基础到异步进化之路
javascript·后端·面试
初雪云11 小时前
iOS 上架入门:证书、描述文件、IPA 上传到底是什么关系?
android·ios·自动化·产品经理·iphone
wear工程师12 小时前
MySQL 索引失效别再背八股:看懂 EXPLAIN,面试官追问也不慌
mysql·面试
玩机达人8814 小时前
一加12/Ace5/Ace3pro手机ColorOS16降级回锁BL熔断变黑砖怎么办
android·智能手机·电脑
Digitally15 小时前
使用 USB 数据线将文件从安卓设备传输到电脑
android·电脑