【问题标题】:Difference in Implementations of Thread Safe Singleton Design Patteren线程安全单例设计模式的实现差异
【发布时间】:2020-06-30 05:45:38
【问题描述】:

thread-safe Singleton Design PatternDouble Check 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;
    }
}

除了Eager初始化之外的以下实现。

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

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

【问题讨论】:

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


    【解决方案1】:

    Double-Check Locking (DCL) 习惯用法是尝试以惰性方式实例化单例对象,其中直到第一次需要时才创建单例对象。 这种方法的问题在于它有缺陷并且不起作用。要深入了解为什么此成语不起作用,请参阅:

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

    DCL 依赖于资源字段的非同步使用。这似乎是无害的,但事实并非如此。要了解原因,假设线程 A 在同步块内,执行语句 resource = new Resource();而线程 B 刚刚进入 getResource()。考虑此初始化对内存的影响。将为新的 Resource 对象分配内存;将调用 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();
    }
    

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

    【讨论】:

    • 第一篇文章最后指出,从JDK5开始,volatile的语义发生了变化,可以使该模式起作用。 stackoverflow.com/questions/3578604/… 描述了如何正确实现它
    • 感谢您的提醒。我已更新答案以包含指向正确 DCL 成语实现的链接。
    • @JustinAlbano 这是非常有用的信息,但我最初的问题意图是问“除了急切初始化之外,第二个实现是否有任何问题?”
    • 不,该实现没有任何问题,尽管可以删除 volatile 关键字。由于单例是static值,在类加载器首次加载类时,Java运行时只会创建一次,因此不需要volatile关键字的保护。
    猜你喜欢
    • 2014-06-13
    • 1970-01-01
    • 1970-01-01
    • 2013-12-18
    • 1970-01-01
    • 2019-03-27
    • 1970-01-01
    • 1970-01-01
    • 2018-09-08
    相关资源
    最近更新 更多