为什么在这个双重检查locking的例子中使用了volatile

从Head Firstdevise模式书中,双重检查locking的单例模式已经实现如下:

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用法是否会volatile使用双重检查的locking(即性能)的目的?

JCIP书籍是理解为什么需要volatile一个很好的资源。 维基百科对这种材料也有一个很好的解释 。

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

正如@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(); } } 

源文章:来自维基百科的初始化on demand-holder_idiom

在软件工程中,初始化需求持有者(devise模式)习语是一个懒惰的单身人士。 在所有版本的Java中,这个习惯用法能够实现安全,高度并发的延迟初始化,性能良好

由于类没有任何staticvariables来初始化,初始化完成。

直到JVM确定必须执行LazyHolder ,其中的静态类定义LazyHolder才会被初始化。

静态类LazyHolder仅在类Singleton上调用静态方法getInstance时执行,并且第一次发生时,JVM将加载并初始化LazyHolder类。

这个解决scheme是线程安全的,不需要特殊的语言结构(即volatilesynchronized )。

那么,没有双重locking性能。 这是一个破碎的模式。

把情绪放在一边,因为没有它,在第二个线程通过instance == null ,第一个线程可能不会构造new Singleton()但没有人承诺创build该对象发生在任何线程的instance 之前实际上创build对象的那个。

volatile依次build立了发生之前的读写关系,并修复了破损的模式。

如果您正在寻找性能,请使用持有者内部静态类。

将variables声明为volatile保证所有对它的访问实际上从内存中读取其当前值。

没有volatile ,编译器可以优化内存访问并将其值保存在一个寄存器中,所以只有variables的第一次使用才能读取包含该variables的实际内存位置。 如果variables被第一次访问和第二次访问之间的另一个线程修改,则这是一个问题; 第一个线程只有第一个(预先修改的)值的副本,所以第二个if语句testingvariables值的陈旧副本。

易失性读取本身并不昂贵。

您可以devise一个testing,以紧密的方式调用getInstance() ,观察volatile读取的影响; 但是那个testing是不现实的; 在这种情况下,程序员通常会调用getInstance()一次,并在使用期间caching实例。

另一个impl是通过使用final字段(见维基百科)。 这需要额外的读取,这可能比volatile版本更昂贵。 final版本可能会在一个紧密的循环中更快,但是这个testing是没有争议的。

如果你没有它,第二个线程可以在第一个设置为null之后进入synchronized块,并且你的本地caching仍然认为它是空的。

第一个不是正确的(如果你是正确的,它会自我失败),而是为了优化。