在Java中使用基于Double Check Locking的Singleton是否安全?

Wol*_*yaa 2 java multithreading final thread-safety java-memory-model

维基百科上列出了Java中的Singleton实现之一:

public class SingletonDemo {
    private static volatile SingletonDemo instance = null;

    private SingletonDemo() {
    }

    public static SingletonDemo getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo.class) {
                if (instance == null) {
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

Java语言规范17条第5款指出,

当构造函数完成时,对象被认为是完全初始化的.在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值.

好吧,想象一下我们的SingletonDemo类有非final字段.那么,并发线程将能够读取默认值而不是构造函数中指定的正确值吗?

Ste*_*n C 5

可以在Java 5及更高版本中正确实现双重检查锁定(DCL).在Java 4及更早版本中,由于volatile未正确指定关于同步的行为(并且在实践中不充分),因此不可能.

您在问题中包含的代码是使用Java 5 JRE或更高版本运行时DCL的正确实现.

但是(IMO),使用DCL是不值得的.特别是如果您(或开发人员追随您)如果正确/安全地不完全理解如何做到这一点.

性能优势实在太小,无法在现实Java应用程序中进行有价值的优化.(如果是的话,你可能会过度使用/滥用单身人士......那会以其他方式咬你!)


好吧,想象一下我们的SingletonDemo类有非final字段.那么,并发线程将能够读取默认值而不是构造函数中指定的正确值吗?

(引用的JLS文本是关于完全不同的情况.它是关于final字段.这里没有相关性.并且你不能finalfinal没有同步的字段的行为推断同步的非字段的行为.)

您的问题的答案是否定的.您的问题中的代码足以保证并发线程不会看到默认值.要了解原因,请阅读以下内容:

  • JLS的第17.4节中的所有内容,以及
  • Goetz等人的最后一章"实践中的Java并发",其中包括关于DCL的一节(如果我没记错的话......)