volatile 使用场景全解析:它能保证什么,又解决不了什么

Android 开发里很容易遇到这种问题:后台线程里有个循环任务,主线程点了"取消",你把 running = false 改掉了,可后台线程像没看见一样还在跑。或者你写了一个单例,觉得 if (instance == null) 判断两次就很稳,结果面试官继续追问:为什么这里一定要加 volatile?

一句话结论: volatile 适合解决"一个线程写状态,其他线程读状态"的轻量同步问题;它保证可见性和一定的有序性,但不保证 count++ 这种复合操作的原子性。

本文按 Java 8+ / Android ART 的常见开发场景来讲,底层依据是 Java 内存模型,也就是 JMM。具体到 JVM、ART、CPU 指令和内存屏障会有实现差异,文章里只讲应用开发和面试最需要掌握的那层。

1. 先从一个真的会踩坑的取消任务说起

先看一个很普通的写法:

java 复制代码
class ImageScanTask implements Runnable {
    private boolean running = true;

    public void cancel() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            scanOneImage();
        }
    }
}

如果 run() 在线程 A 里执行,cancel() 在线程 B 里执行,这段代码看起来应该能停下来。但问题是:线程 A 不一定每次循环都重新从共享内存里读取 running。

编译器、运行时和 CPU 都可能做优化。对线程 A 来说,如果没有同步约束,它可能认为 running 在自己的执行路径里没有变化,于是一直用缓存里的旧值。
#mermaid-svg-M0jaRD8yrZ9FeO46{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M0jaRD8yrZ9FeO46 .error-icon{fill:#552222;}#mermaid-svg-M0jaRD8yrZ9FeO46 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M0jaRD8yrZ9FeO46 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .marker.cross{stroke:#333333;}#mermaid-svg-M0jaRD8yrZ9FeO46 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M0jaRD8yrZ9FeO46 p{margin:0;}#mermaid-svg-M0jaRD8yrZ9FeO46 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .cluster-label text{fill:#333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .cluster-label span{color:#333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .cluster-label span p{background-color:transparent;}#mermaid-svg-M0jaRD8yrZ9FeO46 .label text,#mermaid-svg-M0jaRD8yrZ9FeO46 span{fill:#333;color:#333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .node rect,#mermaid-svg-M0jaRD8yrZ9FeO46 .node circle,#mermaid-svg-M0jaRD8yrZ9FeO46 .node ellipse,#mermaid-svg-M0jaRD8yrZ9FeO46 .node polygon,#mermaid-svg-M0jaRD8yrZ9FeO46 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M0jaRD8yrZ9FeO46 .rough-node .label text,#mermaid-svg-M0jaRD8yrZ9FeO46 .node .label text,#mermaid-svg-M0jaRD8yrZ9FeO46 .image-shape .label,#mermaid-svg-M0jaRD8yrZ9FeO46 .icon-shape .label{text-anchor:middle;}#mermaid-svg-M0jaRD8yrZ9FeO46 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-M0jaRD8yrZ9FeO46 .rough-node .label,#mermaid-svg-M0jaRD8yrZ9FeO46 .node .label,#mermaid-svg-M0jaRD8yrZ9FeO46 .image-shape .label,#mermaid-svg-M0jaRD8yrZ9FeO46 .icon-shape .label{text-align:center;}#mermaid-svg-M0jaRD8yrZ9FeO46 .node.clickable{cursor:pointer;}#mermaid-svg-M0jaRD8yrZ9FeO46 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .arrowheadPath{fill:#333333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M0jaRD8yrZ9FeO46 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M0jaRD8yrZ9FeO46 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-M0jaRD8yrZ9FeO46 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M0jaRD8yrZ9FeO46 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-M0jaRD8yrZ9FeO46 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M0jaRD8yrZ9FeO46 .cluster text{fill:#333;}#mermaid-svg-M0jaRD8yrZ9FeO46 .cluster span{color:#333;}#mermaid-svg-M0jaRD8yrZ9FeO46 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M0jaRD8yrZ9FeO46 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-M0jaRD8yrZ9FeO46 rect.text{fill:none;stroke-width:0;}#mermaid-svg-M0jaRD8yrZ9FeO46 .icon-shape,#mermaid-svg-M0jaRD8yrZ9FeO46 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M0jaRD8yrZ9FeO46 .icon-shape p,#mermaid-svg-M0jaRD8yrZ9FeO46 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-M0jaRD8yrZ9FeO46 .icon-shape .label rect,#mermaid-svg-M0jaRD8yrZ9FeO46 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M0jaRD8yrZ9FeO46 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-M0jaRD8yrZ9FeO46 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-M0jaRD8yrZ9FeO46 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程 A 执行循环任务
读取 running 为 true
继续使用旧值
线程 B 调用 cancel
写入 running 为 false
没有同步关系
线程 A 可能继续循环

这不是说每台机器都会复现,也不是说普通变量一定看不到新值。真正的问题是:Java 内存模型没有给这段代码一个"必须看见"的保证。所以线上偶现一次,就已经够难查了。

把字段改成 volatile:

java 复制代码
class ImageScanTask implements Runnable {
    private volatile boolean running = true;

    public void cancel() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            scanOneImage();
        }
    }
}

这时线程 B 对 running 的写入,对线程 A 后续读取 running 是可见的。用更面试一点的话说:对同一个 volatile 字段的写,happens-before 后续对这个字段的读。

2. volatile 到底保证什么

别急着背"可见性、有序性、不保证原子性"。这句话对,但太像口号。我们换成人话。

2.1 可见性:别人改了,我下次读要能看见

普通字段的问题是:线程之间读写同一个变量,如果没有同步关系,读线程可能看到旧值。

volatile 的第一层作用就是给这个字段建立可见性规则。一个线程写入 volatile 字段后,其他线程后续读取这个字段,要能看到这次写入,或者看到更晚的写入。
#mermaid-svg-6Bvbc7MrxzTUVC1f{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6Bvbc7MrxzTUVC1f .error-icon{fill:#552222;}#mermaid-svg-6Bvbc7MrxzTUVC1f .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6Bvbc7MrxzTUVC1f .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .marker.cross{stroke:#333333;}#mermaid-svg-6Bvbc7MrxzTUVC1f svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6Bvbc7MrxzTUVC1f p{margin:0;}#mermaid-svg-6Bvbc7MrxzTUVC1f .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .cluster-label text{fill:#333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .cluster-label span{color:#333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .cluster-label span p{background-color:transparent;}#mermaid-svg-6Bvbc7MrxzTUVC1f .label text,#mermaid-svg-6Bvbc7MrxzTUVC1f span{fill:#333;color:#333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .node rect,#mermaid-svg-6Bvbc7MrxzTUVC1f .node circle,#mermaid-svg-6Bvbc7MrxzTUVC1f .node ellipse,#mermaid-svg-6Bvbc7MrxzTUVC1f .node polygon,#mermaid-svg-6Bvbc7MrxzTUVC1f .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6Bvbc7MrxzTUVC1f .rough-node .label text,#mermaid-svg-6Bvbc7MrxzTUVC1f .node .label text,#mermaid-svg-6Bvbc7MrxzTUVC1f .image-shape .label,#mermaid-svg-6Bvbc7MrxzTUVC1f .icon-shape .label{text-anchor:middle;}#mermaid-svg-6Bvbc7MrxzTUVC1f .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6Bvbc7MrxzTUVC1f .rough-node .label,#mermaid-svg-6Bvbc7MrxzTUVC1f .node .label,#mermaid-svg-6Bvbc7MrxzTUVC1f .image-shape .label,#mermaid-svg-6Bvbc7MrxzTUVC1f .icon-shape .label{text-align:center;}#mermaid-svg-6Bvbc7MrxzTUVC1f .node.clickable{cursor:pointer;}#mermaid-svg-6Bvbc7MrxzTUVC1f .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .arrowheadPath{fill:#333333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6Bvbc7MrxzTUVC1f .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6Bvbc7MrxzTUVC1f .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6Bvbc7MrxzTUVC1f .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6Bvbc7MrxzTUVC1f .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6Bvbc7MrxzTUVC1f .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6Bvbc7MrxzTUVC1f .cluster text{fill:#333;}#mermaid-svg-6Bvbc7MrxzTUVC1f .cluster span{color:#333;}#mermaid-svg-6Bvbc7MrxzTUVC1f div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6Bvbc7MrxzTUVC1f .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6Bvbc7MrxzTUVC1f rect.text{fill:none;stroke-width:0;}#mermaid-svg-6Bvbc7MrxzTUVC1f .icon-shape,#mermaid-svg-6Bvbc7MrxzTUVC1f .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6Bvbc7MrxzTUVC1f .icon-shape p,#mermaid-svg-6Bvbc7MrxzTUVC1f .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6Bvbc7MrxzTUVC1f .icon-shape .label rect,#mermaid-svg-6Bvbc7MrxzTUVC1f .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6Bvbc7MrxzTUVC1f .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6Bvbc7MrxzTUVC1f .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6Bvbc7MrxzTUVC1f :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程 A
写 volatile flag 为 false
建立 happens before 关系
线程 B 后续读取 flag
看到 false 或更晚的新值

注意这里有两个重点:

  1. 规则针对的是同一个 volatile 字段。
  2. 它解决的是"看不看得见",不是"多个操作能不能合成一个整体"。

2.2 有序性:写 volatile 前的普通写,不能被随便挪到后面

第二层作用是有序性。简单说,volatile 写像一次发布,volatile 读像一次获取。

看一个配置发布的例子:

java 复制代码
class ConfigCenter {
    private Map<String, String> configMap;
    private volatile boolean ready;

    public void load() {
        Map<String, String> local = readFromDisk();
        configMap = local;
        ready = true;
    }

    public String get(String key) {
        if (!ready) {
            return null;
        }
        return configMap.get(key);
    }
}

当线程 A 执行 ready = true 之前,它已经把 configMap 设置好了。线程 B 读到 ready == true 之后,能看到线程 A 在写 ready 之前做过的那些写入。

这个场景里,ready 不只是一个布尔值,它更像一个"发布完成"的信号。
#mermaid-svg-oGiMxLmGxQSlRtRH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oGiMxLmGxQSlRtRH .error-icon{fill:#552222;}#mermaid-svg-oGiMxLmGxQSlRtRH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oGiMxLmGxQSlRtRH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oGiMxLmGxQSlRtRH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oGiMxLmGxQSlRtRH .marker.cross{stroke:#333333;}#mermaid-svg-oGiMxLmGxQSlRtRH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oGiMxLmGxQSlRtRH p{margin:0;}#mermaid-svg-oGiMxLmGxQSlRtRH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oGiMxLmGxQSlRtRH .cluster-label text{fill:#333;}#mermaid-svg-oGiMxLmGxQSlRtRH .cluster-label span{color:#333;}#mermaid-svg-oGiMxLmGxQSlRtRH .cluster-label span p{background-color:transparent;}#mermaid-svg-oGiMxLmGxQSlRtRH .label text,#mermaid-svg-oGiMxLmGxQSlRtRH span{fill:#333;color:#333;}#mermaid-svg-oGiMxLmGxQSlRtRH .node rect,#mermaid-svg-oGiMxLmGxQSlRtRH .node circle,#mermaid-svg-oGiMxLmGxQSlRtRH .node ellipse,#mermaid-svg-oGiMxLmGxQSlRtRH .node polygon,#mermaid-svg-oGiMxLmGxQSlRtRH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oGiMxLmGxQSlRtRH .rough-node .label text,#mermaid-svg-oGiMxLmGxQSlRtRH .node .label text,#mermaid-svg-oGiMxLmGxQSlRtRH .image-shape .label,#mermaid-svg-oGiMxLmGxQSlRtRH .icon-shape .label{text-anchor:middle;}#mermaid-svg-oGiMxLmGxQSlRtRH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oGiMxLmGxQSlRtRH .rough-node .label,#mermaid-svg-oGiMxLmGxQSlRtRH .node .label,#mermaid-svg-oGiMxLmGxQSlRtRH .image-shape .label,#mermaid-svg-oGiMxLmGxQSlRtRH .icon-shape .label{text-align:center;}#mermaid-svg-oGiMxLmGxQSlRtRH .node.clickable{cursor:pointer;}#mermaid-svg-oGiMxLmGxQSlRtRH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oGiMxLmGxQSlRtRH .arrowheadPath{fill:#333333;}#mermaid-svg-oGiMxLmGxQSlRtRH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oGiMxLmGxQSlRtRH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oGiMxLmGxQSlRtRH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oGiMxLmGxQSlRtRH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oGiMxLmGxQSlRtRH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oGiMxLmGxQSlRtRH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oGiMxLmGxQSlRtRH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oGiMxLmGxQSlRtRH .cluster text{fill:#333;}#mermaid-svg-oGiMxLmGxQSlRtRH .cluster span{color:#333;}#mermaid-svg-oGiMxLmGxQSlRtRH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oGiMxLmGxQSlRtRH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oGiMxLmGxQSlRtRH rect.text{fill:none;stroke-width:0;}#mermaid-svg-oGiMxLmGxQSlRtRH .icon-shape,#mermaid-svg-oGiMxLmGxQSlRtRH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oGiMxLmGxQSlRtRH .icon-shape p,#mermaid-svg-oGiMxLmGxQSlRtRH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oGiMxLmGxQSlRtRH .icon-shape .label rect,#mermaid-svg-oGiMxLmGxQSlRtRH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oGiMxLmGxQSlRtRH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oGiMxLmGxQSlRtRH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oGiMxLmGxQSlRtRH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程 A 读取配置
写入 configMap
写 volatile ready 为 true
线程 B 读取 ready 为 true
线程 B 安全读取 configMap 引用

这里最好别把话说过头。volatile 能保证引用发布的可见性,但如果 configMap 后面还被多个线程继续修改,那又是另一个并发问题。更稳的做法是发布一个不可变快照,比如新建一个 Map 后再整体替换引用。

3. volatile 不保证什么

volatile 最容易被误用的地方,是把"可见"误解成"线程安全"。

3.1 count++ 不是原子操作

很多人会写:

java 复制代码
class Counter {
    private volatile int count = 0;

    public void increase() {
        count++;
    }
}

这段代码仍然不是线程安全的。因为 count++ 至少包含三步:

text 复制代码
读取 count
加一
写回 count

两个线程可以同时读到 0,然后都写回 1。最后你明明加了两次,结果只涨了一次。
volatile count ThreadB ThreadA volatile count ThreadB ThreadA #mermaid-svg-OpxgqoLTTutFzXJY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OpxgqoLTTutFzXJY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OpxgqoLTTutFzXJY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OpxgqoLTTutFzXJY .error-icon{fill:#552222;}#mermaid-svg-OpxgqoLTTutFzXJY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OpxgqoLTTutFzXJY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OpxgqoLTTutFzXJY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OpxgqoLTTutFzXJY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OpxgqoLTTutFzXJY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OpxgqoLTTutFzXJY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OpxgqoLTTutFzXJY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OpxgqoLTTutFzXJY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OpxgqoLTTutFzXJY .marker.cross{stroke:#333333;}#mermaid-svg-OpxgqoLTTutFzXJY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OpxgqoLTTutFzXJY p{margin:0;}#mermaid-svg-OpxgqoLTTutFzXJY .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OpxgqoLTTutFzXJY text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-OpxgqoLTTutFzXJY .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-OpxgqoLTTutFzXJY .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-OpxgqoLTTutFzXJY .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-OpxgqoLTTutFzXJY .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-OpxgqoLTTutFzXJY #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-OpxgqoLTTutFzXJY .sequenceNumber{fill:white;}#mermaid-svg-OpxgqoLTTutFzXJY #sequencenumber{fill:#333;}#mermaid-svg-OpxgqoLTTutFzXJY #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-OpxgqoLTTutFzXJY .messageText{fill:#333;stroke:none;}#mermaid-svg-OpxgqoLTTutFzXJY .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OpxgqoLTTutFzXJY .labelText,#mermaid-svg-OpxgqoLTTutFzXJY .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-OpxgqoLTTutFzXJY .loopText,#mermaid-svg-OpxgqoLTTutFzXJY .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-OpxgqoLTTutFzXJY .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-OpxgqoLTTutFzXJY .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-OpxgqoLTTutFzXJY .noteText,#mermaid-svg-OpxgqoLTTutFzXJY .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-OpxgqoLTTutFzXJY .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OpxgqoLTTutFzXJY .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OpxgqoLTTutFzXJY .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OpxgqoLTTutFzXJY .actorPopupMenu{position:absolute;}#mermaid-svg-OpxgqoLTTutFzXJY .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-OpxgqoLTTutFzXJY .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OpxgqoLTTutFzXJY .actor-man circle,#mermaid-svg-OpxgqoLTTutFzXJY line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-OpxgqoLTTutFzXJY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} read 0 1 read 0 2 write 1 3 write 1 4

volatile 保证每次读写都是"看得见"的,但它不能把"读、改、写"这三步打包成一个不可拆的动作。

计数器应该用:

java 复制代码
class Counter {
    private final AtomicInteger count = new AtomicInteger(0);

    public int increase() {
        return count.incrementAndGet();
    }
}

AtomicInteger 内部也会用到 volatile 语义,但它还会配合 CAS。也就是说,volatile 解决可见性,CAS 才解决"比较并更新"的原子竞争。

3.2 多字段一致性不是 volatile 能兜住的

再看一个常见错误:

java 复制代码
class UserState {
    private volatile boolean login;
    private String userId;

    public void login(String id) {
        userId = id;
        login = true;
    }

    public String currentUserId() {
        return login ? userId : null;
    }
}

这段代码在"登录后读取 userId"这种单向发布场景里,看起来还行。但如果后面有退出、切账号、多字段更新,就会变复杂:

java 复制代码
public void logout() {
    login = false;
    userId = null;
}

当状态由多个字段共同表达时,读线程可能看到"新 login + 旧 userId",或者"旧 login + 新 userId"这种组合。volatile 没法保证多个字段作为一个整体同时变化。

这种场景更推荐整体不可变快照:

java 复制代码
final class SessionSnapshot {
    final boolean login;
    final String userId;

    SessionSnapshot(boolean login, String userId) {
        this.login = login;
        this.userId = userId;
    }
}

class SessionStore {
    private volatile SessionSnapshot snapshot =
            new SessionSnapshot(false, null);

    public void login(String userId) {
        snapshot = new SessionSnapshot(true, userId);
    }

    public void logout() {
        snapshot = new SessionSnapshot(false, null);
    }

    public SessionSnapshot current() {
        return snapshot;
    }
}

这样读线程每次拿到的是一整份状态,而不是几个字段拼出来的半成品。

4. 适合用 volatile 的场景

4.1 停止线程或取消任务的标记位

这是最经典、也最容易理解的场景。

java 复制代码
class UploadWorker implements Runnable {
    private volatile boolean cancelled;

    public void cancel() {
        cancelled = true;
    }

    @Override
    public void run() {
        while (!cancelled) {
            uploadOneChunk();
        }
        releaseTempFile();
    }
}

它适合的前提是:任务能定期回到循环里检查这个标记。如果线程卡在 I/O、锁等待、sleep() 或阻塞队列里,单靠 volatile 不一定能及时停下来。

更完整的取消通常会配合:

text 复制代码
volatile 标记:让循环知道该停
Thread.interrupt:唤醒可中断阻塞
Future.cancel:统一取消任务
协程 Job.cancel:结构化取消

所以面试里可以说:volatile 适合做轻量取消标记,但不是完整取消框架。

4.2 一次性状态发布

比如初始化完成:

java 复制代码
class ResourceManager {
    private Resource resource;
    private volatile boolean initialized;

    public void init(Context context) {
        resource = new Resource(context.getApplicationContext());
        initialized = true;
    }

    public Resource get() {
        if (!initialized) {
            throw new IllegalStateException("Resource not initialized");
        }
        return resource;
    }
}

这里 initialized 的作用不是保护复杂操作,而是告诉其他线程:"前面的初始化动作已经发布出来了。"

但如果初始化可能被多个线程同时触发,光靠这个布尔值仍然不够。你还要用 synchronized、CAS 或者启动阶段的单线程初始化约束,保证初始化过程本身只发生一次。

4.3 双重检查单例

双重检查单例是 volatile 面试里绕不开的例子。

java 复制代码
class ImageLoader {
    private static volatile ImageLoader instance;

    private ImageLoader() {
    }

    public static ImageLoader getInstance() {
        ImageLoader local = instance;
        if (local == null) {
            synchronized (ImageLoader.class) {
                local = instance;
                if (local == null) {
                    local = new ImageLoader();
                    instance = local;
                }
            }
        }
        return local;
    }
}

为什么 instance 要加 volatile?因为 new ImageLoader() 不是一个单纯动作,可以粗略拆成:

text 复制代码
分配内存
执行构造方法
把引用赋值给 instance

如果没有合适的有序性约束,其他线程可能看到一个"引用不为空,但对象还没完整初始化"的状态。volatile 的发布语义可以避免这类重排序带来的问题。
#mermaid-svg-lsvhl14232jhHrFO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lsvhl14232jhHrFO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lsvhl14232jhHrFO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lsvhl14232jhHrFO .error-icon{fill:#552222;}#mermaid-svg-lsvhl14232jhHrFO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lsvhl14232jhHrFO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lsvhl14232jhHrFO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lsvhl14232jhHrFO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lsvhl14232jhHrFO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lsvhl14232jhHrFO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lsvhl14232jhHrFO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lsvhl14232jhHrFO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lsvhl14232jhHrFO .marker.cross{stroke:#333333;}#mermaid-svg-lsvhl14232jhHrFO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lsvhl14232jhHrFO p{margin:0;}#mermaid-svg-lsvhl14232jhHrFO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lsvhl14232jhHrFO .cluster-label text{fill:#333;}#mermaid-svg-lsvhl14232jhHrFO .cluster-label span{color:#333;}#mermaid-svg-lsvhl14232jhHrFO .cluster-label span p{background-color:transparent;}#mermaid-svg-lsvhl14232jhHrFO .label text,#mermaid-svg-lsvhl14232jhHrFO span{fill:#333;color:#333;}#mermaid-svg-lsvhl14232jhHrFO .node rect,#mermaid-svg-lsvhl14232jhHrFO .node circle,#mermaid-svg-lsvhl14232jhHrFO .node ellipse,#mermaid-svg-lsvhl14232jhHrFO .node polygon,#mermaid-svg-lsvhl14232jhHrFO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lsvhl14232jhHrFO .rough-node .label text,#mermaid-svg-lsvhl14232jhHrFO .node .label text,#mermaid-svg-lsvhl14232jhHrFO .image-shape .label,#mermaid-svg-lsvhl14232jhHrFO .icon-shape .label{text-anchor:middle;}#mermaid-svg-lsvhl14232jhHrFO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lsvhl14232jhHrFO .rough-node .label,#mermaid-svg-lsvhl14232jhHrFO .node .label,#mermaid-svg-lsvhl14232jhHrFO .image-shape .label,#mermaid-svg-lsvhl14232jhHrFO .icon-shape .label{text-align:center;}#mermaid-svg-lsvhl14232jhHrFO .node.clickable{cursor:pointer;}#mermaid-svg-lsvhl14232jhHrFO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lsvhl14232jhHrFO .arrowheadPath{fill:#333333;}#mermaid-svg-lsvhl14232jhHrFO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lsvhl14232jhHrFO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lsvhl14232jhHrFO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lsvhl14232jhHrFO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lsvhl14232jhHrFO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lsvhl14232jhHrFO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lsvhl14232jhHrFO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lsvhl14232jhHrFO .cluster text{fill:#333;}#mermaid-svg-lsvhl14232jhHrFO .cluster span{color:#333;}#mermaid-svg-lsvhl14232jhHrFO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lsvhl14232jhHrFO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lsvhl14232jhHrFO rect.text{fill:none;stroke-width:0;}#mermaid-svg-lsvhl14232jhHrFO .icon-shape,#mermaid-svg-lsvhl14232jhHrFO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lsvhl14232jhHrFO .icon-shape p,#mermaid-svg-lsvhl14232jhHrFO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lsvhl14232jhHrFO .icon-shape .label rect,#mermaid-svg-lsvhl14232jhHrFO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lsvhl14232jhHrFO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lsvhl14232jhHrFO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lsvhl14232jhHrFO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
调用 getInstance
instance 是空
直接返回
进入 synchronized
再次判断为空
创建对象
写 volatile instance

不过工程里我不建议为了展示技术感到处写双重检查。Java 可以用静态内部类,Kotlin 可以用 object 或 by lazy。双重检查适合拿来理解 JMM,不一定是业务代码的首选。

4.4 读多写少的配置快照

比如灰度开关、实验配置、网络策略:

java 复制代码
final class FeatureConfig {
    final boolean newHomeEnabled;
    final int maxRetryCount;
    final String apiHost;

    FeatureConfig(boolean newHomeEnabled, int maxRetryCount, String apiHost) {
        this.newHomeEnabled = newHomeEnabled;
        this.maxRetryCount = maxRetryCount;
        this.apiHost = apiHost;
    }
}

class FeatureConfigStore {
    private volatile FeatureConfig current =
            new FeatureConfig(false, 1, "https://api.example.com");

    public void update(FeatureConfig newConfig) {
        current = newConfig;
    }

    public FeatureConfig get() {
        return current;
    }
}

这个写法有两个关键点:

  1. FeatureConfig 尽量不可变。
  2. 更新时整体替换引用,而不是在旧对象上改字段。

这样读线程不需要加锁,能拿到某一刻完整的配置快照。它适合读很多、写很少、允许读到"刚才那一版"的场景。

4.5 Android 里后台线程和主线程共享简单状态

比如后台任务需要看一个开关:

java 复制代码
class PagePreloader {
    private volatile boolean pageDestroyed;

    public void onDestroy() {
        pageDestroyed = true;
    }

    public void preload() {
        ioExecutor.execute(() -> {
            if (pageDestroyed) {
                return;
            }

            Data data = loadData();

            if (pageDestroyed) {
                return;
            }

            mainHandler.post(() -> {
                if (!pageDestroyed) {
                    render(data);
                }
            });
        });
    }
}

这类代码里,volatile 只能解决"后台线程能不能看见页面已经销毁"这件事。它不能替代 Android 的主线程规则。真正更新 UI,仍然要回到主线程,比如用 Handler、Dispatchers.Main、runOnUiThread() 或生命周期感知组件。

如果你已经在用协程,很多时候更推荐用结构化取消:

kotlin 复制代码
class DemoViewModel : ViewModel() {
    fun load() {
        viewModelScope.launch {
            val data = withContext(Dispatchers.IO) {
                loadData()
            }
            renderState(data)
        }
    }
}

协程取消比手写一个 volatile cancelled 更系统,因为它能沿着父子 Job 传播。但理解 volatile 仍然有必要,因为你读 Java 并发包、AndroidX 源码、三方库源码时会经常看到它。

5. 源码里为什么经常能看到 volatile

源码里使用 volatile,通常不是为了"加个线程安全魔法",而是为了表达一种状态发布和跨线程观察。

源码位置 volatile 用在什么地方 想解决的问题
FutureTask state、runner、waiters 任务状态、运行线程、等待队列需要被多个线程观察和推进
AtomicInteger 内部 value 普通读写有 volatile 语义,更新操作再配合 CAS
AndroidX LiveData mData、mPendingData 后台 postValue() 和主线程派发之间共享最新值

以 FutureTask 为例,它有任务状态:新建、完成、异常、取消、中断中、中断完成。调用 get() 的线程、执行任务的线程、取消任务的线程,都会关心这个状态。状态字段如果不可见,isDone()、cancel()、get() 这种 API 就没法可靠协作。

以 LiveData 为例,postValue() 可以在后台线程调用,但真正的 setValue() 要切回主线程。源码里 mPendingData 既被后台线程写,也会被主线程的 runnable 读取和清空。它还会配合同步块和主线程调度一起工作,不是单靠一个 volatile 解决全部问题。

这就是读源码时最重要的判断:看到 volatile,先问它是不是一个跨线程状态点;再看有没有 CAS、锁、队列或主线程调度一起配合。

6. 不适合用 volatile 的场景

6.1 计数器、自增、自减

错误:

java 复制代码
private volatile int count;

void add() {
    count++;
}

正确方向:

java 复制代码
private final AtomicInteger count = new AtomicInteger();

void add() {
    count.incrementAndGet();
}

如果高并发统计特别频繁,可以继续看 LongAdder。它通过分段减少竞争,比所有线程抢一个数更适合热点计数。

6.2 先判断再修改

这种也不安全:

java 复制代码
private volatile boolean started;

void start() {
    if (!started) {
        started = true;
        doStart();
    }
}

两个线程可能同时看到 started == false,然后都进入 doStart()。

应该用锁或者 CAS:

java 复制代码
private final AtomicBoolean started = new AtomicBoolean(false);

void start() {
    if (started.compareAndSet(false, true)) {
        doStart();
    }
}

判断和修改必须作为一个整体时,volatile 就不够了。

6.3 多个字段必须一起保持一致

比如余额和版本号、登录态和用户信息、缓存对象和过期时间。如果这几个字段必须一起变化,优先考虑:

text 复制代码
synchronized
ReentrantLock
不可变快照加 volatile 引用
AtomicReference 包一整个状态对象

不要给每个字段都加 volatile,然后期待它们自动组成一个事务。

6.4 等待条件成立

有些人会写忙等:

java 复制代码
while (!ready) {
}

哪怕 ready 是 volatile,这个循环也会一直占 CPU。等待条件更合适的工具是:

text 复制代码
CountDownLatch
Condition
wait 和 notify
BlockingQueue
CompletableFuture
协程挂起函数
Flow 或 LiveData

volatile 适合"顺手看一眼状态",不适合"在这里一直干等"。

7. 和 synchronized、Atomic、final 怎么区分

这几个概念经常一起被问。

工具 解决什么 适合场景 代价
volatile 可见性、有序性 简单状态标记、引用发布 不保证复合操作原子性
synchronized 互斥、可见性、有序性 临界区、多字段一致性 可能阻塞,粒度要设计好
AtomicInteger 等 原子更新、可见性 计数器、CAS 状态机 复杂逻辑可读性会下降
final 构造后不可变语义 不可变对象、安全发布辅助 字段本身不能再变
Handler 线程切换和消息排队 Android 主线程通信 不等于共享变量锁
协程 Job 结构化取消 Kotlin 异步任务 要遵守协程作用域

一句话区分:

text 复制代码
只需要别人看见一个新状态,用 volatile。
需要一组代码同一时间只能一个线程执行,用 synchronized 或 Lock。
需要无锁地原子更新一个值,用 Atomic。
需要 Android 线程切换,用 Handler 或协程调度器。
需要生命周期内自动取消,用 viewModelScope、lifecycleScope。

8. 面试官为什么喜欢问 volatile

因为它能顺着问出一整串并发基础:

  1. 你是否知道 JMM。
  2. 你是否能区分可见性、原子性、有序性。
  3. 你是否知道 count++ 为什么不安全。
  4. 你是否理解双重检查单例。
  5. 你是否看过 AtomicInteger、FutureTask、LiveData 这类源码。
  6. 你是否能把 Java 并发讲回 Android 场景。

如果只回答"volatile 保证可见性,不保证原子性",面试官大概率会继续追问:那可见性是什么?为什么 count++ 不行?DCL 为什么要加?Android 里哪里见过?

可以按这段口语化回答:

text 复制代码
volatile 我一般把它理解成轻量级同步字段。
它主要保证两个东西:第一是可见性,一个线程写了 volatile 字段,另一个线程后续读这个字段能看到;第二是有序性,写 volatile 之前的普通写,不会被乱序到它后面让读线程看不到。

但它不保证复合操作原子性,比如 count++ 本质上是读、加、写三步,两个线程仍然可能同时读到旧值,所以计数器要用 AtomicInteger 或加锁。

项目里适合用 volatile 的地方,一般是取消标记、初始化完成标记、读多写少的配置快照引用,或者双重检查单例里的 instance。Android 里它也常出现在一些跨线程状态字段上,比如 LiveData 的 pending data。真正涉及 UI 线程切换、生命周期取消、多个字段一致性时,我不会只靠 volatile,会用 Handler、协程作用域、Atomic 或 synchronized。

这段回答有场景、有边界,也不把 volatile 吹成万能线程安全。

9. 一个判断口诀

写代码时可以先问自己四个问题:
#mermaid-svg-GOKoMrZIHoD4374L{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GOKoMrZIHoD4374L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GOKoMrZIHoD4374L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GOKoMrZIHoD4374L .error-icon{fill:#552222;}#mermaid-svg-GOKoMrZIHoD4374L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GOKoMrZIHoD4374L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GOKoMrZIHoD4374L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GOKoMrZIHoD4374L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GOKoMrZIHoD4374L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GOKoMrZIHoD4374L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GOKoMrZIHoD4374L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GOKoMrZIHoD4374L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GOKoMrZIHoD4374L .marker.cross{stroke:#333333;}#mermaid-svg-GOKoMrZIHoD4374L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GOKoMrZIHoD4374L p{margin:0;}#mermaid-svg-GOKoMrZIHoD4374L .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GOKoMrZIHoD4374L .cluster-label text{fill:#333;}#mermaid-svg-GOKoMrZIHoD4374L .cluster-label span{color:#333;}#mermaid-svg-GOKoMrZIHoD4374L .cluster-label span p{background-color:transparent;}#mermaid-svg-GOKoMrZIHoD4374L .label text,#mermaid-svg-GOKoMrZIHoD4374L span{fill:#333;color:#333;}#mermaid-svg-GOKoMrZIHoD4374L .node rect,#mermaid-svg-GOKoMrZIHoD4374L .node circle,#mermaid-svg-GOKoMrZIHoD4374L .node ellipse,#mermaid-svg-GOKoMrZIHoD4374L .node polygon,#mermaid-svg-GOKoMrZIHoD4374L .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GOKoMrZIHoD4374L .rough-node .label text,#mermaid-svg-GOKoMrZIHoD4374L .node .label text,#mermaid-svg-GOKoMrZIHoD4374L .image-shape .label,#mermaid-svg-GOKoMrZIHoD4374L .icon-shape .label{text-anchor:middle;}#mermaid-svg-GOKoMrZIHoD4374L .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GOKoMrZIHoD4374L .rough-node .label,#mermaid-svg-GOKoMrZIHoD4374L .node .label,#mermaid-svg-GOKoMrZIHoD4374L .image-shape .label,#mermaid-svg-GOKoMrZIHoD4374L .icon-shape .label{text-align:center;}#mermaid-svg-GOKoMrZIHoD4374L .node.clickable{cursor:pointer;}#mermaid-svg-GOKoMrZIHoD4374L .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GOKoMrZIHoD4374L .arrowheadPath{fill:#333333;}#mermaid-svg-GOKoMrZIHoD4374L .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GOKoMrZIHoD4374L .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GOKoMrZIHoD4374L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GOKoMrZIHoD4374L .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GOKoMrZIHoD4374L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GOKoMrZIHoD4374L .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GOKoMrZIHoD4374L .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GOKoMrZIHoD4374L .cluster text{fill:#333;}#mermaid-svg-GOKoMrZIHoD4374L .cluster span{color:#333;}#mermaid-svg-GOKoMrZIHoD4374L div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GOKoMrZIHoD4374L .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GOKoMrZIHoD4374L rect.text{fill:none;stroke-width:0;}#mermaid-svg-GOKoMrZIHoD4374L .icon-shape,#mermaid-svg-GOKoMrZIHoD4374L .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GOKoMrZIHoD4374L .icon-shape p,#mermaid-svg-GOKoMrZIHoD4374L .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GOKoMrZIHoD4374L .icon-shape .label rect,#mermaid-svg-GOKoMrZIHoD4374L .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GOKoMrZIHoD4374L .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GOKoMrZIHoD4374L .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GOKoMrZIHoD4374L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
是
否
是
否
我要不要用 volatile
只是一个线程写状态
考虑锁 原子类 或并发容器
其他线程只是读取状态
是否需要多个字段一致
用不可变快照或锁
是否有自增或先判断再修改
用 Atomic 或 synchronized
可以考虑 volatile

大白话版:

text 复制代码
一个字段,一个写方,多个读方,只看状态,不做复合更新,volatile 才舒服。

只要出现"多个字段一起变""先判断再修改""自增统计""等待条件""复杂生命周期",就别硬上 volatile。

10. 小结

volatile 的定位其实很窄,但正因为窄,所以用对了很舒服。

它不是锁,也不是线程安全万能药。它解决的是跨线程状态的可见性和发布顺序问题。停止标记、初始化完成标记、双重检查单例、配置快照引用,这些场景都很适合它。计数器、多字段一致性、集合并发修改、等待条件成立,这些场景就别勉强了。

再把开头那句话收回来:volatile 适合"一个线程写状态,其他线程读状态"的轻量同步;它保证可见性和有序性,但不保证 count++ 这种复合操作的原子性。

11. 源码与参考入口

以下入口按 2026-09-30 整理,用来辅助定位规则和源码职责;具体实现请以项目锁定版本为准。

主题 链接
Java 语言规范:Happens-before 规则 JLS 17.4.5 Happens-before Order
Java 语言规范:volatile long 和 double 的原子读写 JLS 17.7 Non-Atomic Treatment of double and long
OpenJDK AtomicInteger AtomicInteger.java
OpenJDK FutureTask FutureTask.java
AndroidX LiveData LiveData.java
相关推荐
蒲公英内测分发2 小时前
智能投影仪参加海外展会,未上架的 Android 配套 App 怎么让客户限时试用?
android
ii_best2 小时前
按键精灵手机端开发安卓版实战:手写一个可最小化、可拖动的「运行日志悬浮窗」(附完整 源码 + 踩坑记录)
android·ios·智能手机·自动化·ai编程·按键精灵
终端安全笔记2 小时前
安卓做 MDM 管控,先分清三种注册入口
android·安全·智能手机
乌萨达4 小时前
手机模拟器安卓怎么用?电脑安装应用、玩手游与账号同步指南
android·智能手机·电脑·雷电模拟器
美狐美颜SDK开放平台4 小时前
Android与iOS直播APP美颜有什么区别?视频美颜SDK开发详解
android·ios·音视频·视频美颜sdk
春猿火6 小时前
某市-2026【网安·论道】misc-afterimage_note-wp
android
恋猫de小郭7 小时前
Shopify 回应为什么从 RN 回到原生,为什么不用 KMP ?
android·前端·flutter
fundroid9 小时前
Android AI 开发,真正拉开差距的是工程闭环
android·ai·大模型·agent
脚踏实地,坚持不懈!9 小时前
Android 面试题全解(结合 AOSP 源码 · 截至 Android 17 / API 37)
android