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

toc*_*777 70 java singleton design-patterns locking double-checked-locking

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我不明白为什么volatile被使用.volatile使用不会 破坏使用双重检查锁定的目的,即性能?

Tim*_*der 63

理解为什么一个很好的资源volatile需要来自JCIP书.维基百科对该材料也有不错的解释.

真正的问题是Thread A可以instance在完成构建之前为其分配内存空间instance.Thread B将看到该任务并尝试使用它.这导致Thread B失败,因为它使用的是部分构造的版本instance.

  • @Tim好吧,XML中的单身人士仍然是单身人士; 通过使用DI,了解应用程序的运行时状态并不容易.较小的代码单元可能看起来更简单,代价是强制构造所有单元以符合DI习语(有人可能会说这是一件好事).对单身人士的指责是不公平的,它将API与impl混淆 - "Foo.getInstance()`只是一个表达式以某种方式得到一个Foo,它与`@Inject Foo foo`没有什么不同; 无论哪种方式,请求Foo的站点都不知道返回哪个Foo以及静态和运行时依赖关系是如何相同的. (6认同)
  • @ toc777`volatile`比通常提交的要慢.如果你寻找性能,那就选择持有者级的模式.`volatile`在这里只是为了表明*有一种方法可以使破碎模式发挥作用.它更像是一个编码挑战,而不是一个真正的问题. (5认同)
  • @irreputable你知道有趣的是,在我们进行这次交流的时候,我从来没有使用过Spring而且并没有提到Spring那个不可思议的DI.Singleton作为一个`静态``工厂`的真正危险是在代码中深入调用它的诱惑,它不应该知道`getInstance()`方法,而是要求提供一个实例. (2认同)
  • `真正的问题是线程A可能会在完成实例构建之前分配内存空间。`那么`volatile`如何解决这个问题? (2认同)

Rav*_*abu 18

正如@irreputable引用的那样,不稳定并不昂贵.即使价格昂贵,也应优先考虑性能.

Lazy Singletons还有一个更干净优雅的方式.

public final class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
Run Code Online (Sandbox Code Playgroud)

源文章:来自维基百科的initialization-on-demand_holder_idiom

在软件工程中,Initialization on Demand Holder(设计模式)习语是一个懒惰的单例.在所有版本的Java中,这个习惯用法都能实现安全,高度并发的延迟初始化,并具有良好的性能

由于该类没有任何static初始化变量,因此初始化很简单.

LazyHolder在JVM确定必须执行LazyHolder之前,不会初始化其中的静态类定义.

静态类LazyHoldergetInstance在类Singleton上调用静态方法时执行,并且第一次发生这种情况时,JVM将加载并初始化LazyHolder类.

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


alf*_*alf 10

好吧,没有双重检查锁定的性能.这是一个破碎的模式.

抛开情绪,这volatile是因为没有它,当第二个线程通过时instance == null,第一个线程可能还没有构建new Singleton():没有人承诺对象的创建发生 - 在instance为任何线程分配之前,但实际创建对象的线程.

volatile反过来确定读取和写入之间发生的关系,并修复损坏的模式.

如果您正在寻找性能,请使用holder inner static class.

  • @AarishRamesh,不为空;任何状态。有两个操作:将地址分配给“instance”变量,以及在该地址实际创建对象。除非有强制同步的东西,比如“易失性”访问或显式同步,否则第二个线程可能会乱序获取这两个事件。 (2认同)