线程安全单例设计模式的实现差异

时间:2020-06-30 05:45:38

标签: design-patterns thread-safety singleton double-checked-locking

如以下代码所示,线程安全的单例设计模式双重检查锁定之间有什么区别。

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (this) {
                if(instance==null)
                    instance = new Singleton();
             }
        }
        return instance;
    }
}

在下面的实现中,除了渴望初始化之外。

public class Singleton {
    private static volatile Singleton instance = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

1 个答案:

答案 0 :(得分:1)

双重检查锁定(DCL)惯用法是一种尝试以惰性方式实例化单例对象的方法,该方法直到第一次需要时才创建单例对象。 这种方法的问题在于它有缺陷,并且不起作用。要深入了解为什么这种用法不起作用,请参阅:

虽然Brian Goetz详细介绍了成语失败的原因,但为了简洁起见,可以在以下段落中总结该问题:

DCL依赖于资源字段的不同步使用。这似乎无害,但事实并非如此。要了解原因,假设线程A在同步块内部,执行语句resource = new Resource();。而线程B刚刚进入getResource()。考虑此初始化对内存的影响。新资源对象的内存将被分配; Resource的构造函数将被调用,初始化新对象的成员字段;并且SomeClass的字段资源将被分配一个对新创建对象的引用。

简而言之,Java内存模型(JMM)不保证给定两个线程,一个不会看到instance字段的部分构造的实例,因为instance字段是在外部观察到的同步块(即if (instance == null))。

请注意,从JDK 5开始,double-checked locking idiom is possible仍然很微妙,应该在可以使用其他替代方法时避免使用。

开发人员首先尝试创建DCL习惯用法的原因是,使用synchronized关键字可能会导致性能下降(根据Brian Goetz的文章,可能高达100倍)。

第二个模式是单例模式的一种简单得多的方法,因为它使单例对象static得以确保,对于所有尝试访问该单例的外部对象,将仅创建一个对象。这种方法称为渴望实例化,因为instance对象是在任何外部对象需要它之前创建的。除非创建单例对象有大量开销,否则急于实例化通常就足够了。

如果需要延迟实例化,那么一个聪明的方法是创建一个单独的类,仅将单例实例作为static字段:

public class Singleton {
    public static Singleton instance = new Singleton();
}

由于在类加载器加载类之前,不会在Java中初始化static字段,因此除非外部对象引用instance对象,否则instance对象不会被实例化。因此,我们通过编译器和Java运行时环境实现了延迟实例化。

相关问题