首页 > 编程笔记 > Java笔记 阅读:14

Java双重检查锁为什么使用volatile关键字?(新手必看)

双重检查锁(Double-Checked Locking,DCL)以及 volatile 关键字的使用是并发编程中一个非常经典的问题。

在多线程编程中,单例模式的线程安全实现常常会使用到 DCL 模式。这种模式在一定程度上减少了同步的开销,但如果没有正确使用,它会导致复杂的并发问题。Java 语言提供了 volatile 关键字来方便地实现正确的 DCL。

单例模式意味着某个类的实例在整个应用程序中只有一个。在多线程环境下,我们必须确保单例类的对象在多个线程中只被创建一次。最简单的线程安全单例模式实现是对整个创建方法加锁,但这会引入不必要的同步开销,因为一旦实例被创建,就不再需要同步控制了。

为了减少同步带来的开销,DCL 利用两次检查和一次锁定来实现线程安全的单例。第一次检查是为了避免不必要的同步,第二次检查是为了在 null 实例的情况下进行同步创建。

DCL 的示例代码如下:
public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
在上述代码中,instance 使用 volatile 修饰,这是实现 DCL 的关键。很多读者可能会产生疑问,为什么要使用 volatile 关键字呢?它起到了什么作用?

我们知道,volatile 关键字主要有两个作用:
上述示例代码中,一个线程执行过程如下:
上述过程看起来是 3 个步骤,实际上包含很多指令,instance = new Singleton();对象创建过程就可以分解为下面步骤:
如果我们不使用 volatile 关键字修饰,可能会导致这些指令的执行顺序被重排序,例如,指令 3 可能在指令 2 之前执行。这意味着在调用构造函数初始化对象之前,instance 的值已经不是 null 了。如果此时,线程 B 开始了 getInstance() 方法的第一次检查,并且看到 instance 的值不是 null,那么线程 B 将返回一个尚未完全构造的对象。

因此 volatile 关键字是 DCL 的重要部分,它解决了指令重排序问题和变量可见性问题。这确保了实例化操作的安全性,允许我们高效地实现线程安全的单例模式。

相关文章