为什么在双重检查锁定中使用volatile?

12 浏览
0 Comments

为什么在双重检查锁定中使用volatile?

来自《Head First设计模式》的书,双重检查锁定的单例模式被实现如下:

public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

我不明白为什么要使用volatile。使用volatile不是会削弱双重检查锁定的目的,即提高性能吗?

admin 更改状态以发布 2023年5月22日
0
0 Comments

如@irreputable所引述的,volatile不昂贵。即使它很昂贵,一致性应该优先于性能。

还有一种干净优雅的方法可以实现懒惰单例。

public final class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

来源文章:Initialization-on-demand_holder_idiom,来自维基百科

在软件工程中,初始化-on-demand持有者(设计模式)习惯用法是一种懒加载的单例。在Java的所有版本中,这种习惯用法可以实现安全、高度并发的懒初始化和良好的性能。

由于该类不具有任何要初始化的静态变量,因此初始化可以轻松完成。

其内部的静态类定义LazyHolder直到JVM确定必须执行LazyHolder时才会初始化。

只有在类Singleton上调用静态方法getInstance并且第一次发生这种情况时,JVM才会加载和初始化静态类LazyHolder

这种解决方案是线程安全的,无需特殊的语言结构(即volatilesynchronized)。

0
0 Comments

了解为什么需要使用volatile的好资源来自JCIP书籍。维基百科也有一个不错的解释

真正的问题是线程 A在完成构造instance之前可能为instance分配一个内存空间。线程 B将看到该赋值并尝试使用它。这会导致线程 B失败,因为它使用了一个部分构造的instance

0