You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双重检查锁定(DCL)中volatile关键字的必要性及相关疑问

解答:双重检查锁定(DCL)中volatile关键字的必要性

Hey there! 看得出来你已经把《Java并发编程实战》里的DCL知识点啃得很深入了,你的理解完全没错——不用volatile的话,线程确实可能拿到一个“半初始化”的对象。我猜你想问的是**“既然同步代码块已经能保证可见性,为啥还要给resource字段加volatile?”** 这绝对是并发领域里最容易踩坑的误区之一,我来给你讲透:

先复盘下无volatile的DCL到底哪里炸锅

先上一段典型的有问题的DCL代码:

public class Singleton {
    private static Singleton resource; // 没加volatile

    private Singleton() {}

    public static Singleton getInstance() {
        if (resource == null) { // 第一次检查:无锁
            synchronized (Singleton.class) {
                if (resource == null) { // 第二次检查:加锁
                    resource = new Singleton(); // 这里藏着大问题!
                }
            }
        }
        return resource;
    }
}

你可能不知道,resource = new Singleton();这一行在JVM层面会拆成3个独立操作:

  1. 给Singleton对象分配内存空间
  2. 执行构造函数,初始化对象里的所有字段
  3. 把resource变量指向刚分配的内存地址(此时resource不再是null)

在没有volatile的情况下,JVM的即时编译器会搞指令重排序——为了提速,它可能把步骤2和3调换顺序:先把resource赋值为非null的内存地址,再去初始化对象。这时候如果另一个线程刚好走到第一次检查,看到resource不是null,直接就返回这个还没初始化完的“半成品”对象,调用它的方法大概率会出各种诡异的bug(比如空指针、字段值不对)。

为啥同步代码块救不了这个场景?

你可能会疑惑:同步代码块不是能保证原子性和可见性吗?没错,但问题出在第一次检查是在同步块外面做的!

假设线程A在同步块里执行了重排序后的操作:先把resource指向内存,还没初始化对象就退出了同步块。这时候线程B走到if (resource == null),看到resource已经不是null,直接返回了——它根本没进入同步块,所以同步块的可见性保证对它完全无效,自然看不到线程A还没做完的对象初始化。

volatile在这里的核心作用

volatile关键字主要干了两件事,刚好命中DCL的痛点:

  • 禁止指令重排序:JVM会保证对象初始化(步骤2)完成后,才会把resource赋值为非null引用(步骤3),彻底杜绝“半成品”对象被暴露的可能。
  • 保证可见性:当一个线程修改了volatile变量,其他线程能立刻读到最新值。不过这里更关键的是前面的禁止重排序功能——毕竟如果重排序被禁止了,可见性问题其实也顺带解决了。

额外提一句:Java版本的坑

要注意,Java 5之前的volatile语义并没有明确禁止这种重排序,所以那时候哪怕加了volatile,DCL还是有问题。但Java 5重新定义了volatile的内存模型,才真正把DCL的漏洞补上,这也是《Java并发编程实战》里讨论这个问题的前提哦。

内容的提问来源于stack exchange,提问作者sdindiver

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:57:19