9

为什么模式被认为是损坏的?对我来说看起来不错?有任何想法吗?

public static Singleton getInst() {
    if (instace == null) createInst();
    return instace;
}

private static synchronized createInst() {
     if (instace == null) {
         instace = new Singleton(); 
     }
}
4

7 回答 7

21

乍一看还不错,但这种技术有很多微妙的问题,通常应该避免。例如,考虑以下事件序列:

  1. 线程A注意到该值没有被初始化,因此它获得了锁并开始初始化该值。
  2. 在 A 完成初始化之前,允许编译器生成的代码更新共享变量以指向部分构造的对象。
  3. 线程 B 注意到共享变量已被初始化(或者看起来如此),并返回它的值。因为线程 B 认为该值已经初始化,所以它不会获取锁。如果 B 在 B 看到 A 完成的所有初始化之前使用该对象,则程序可能会崩溃。

您可以通过使用“volatile”关键字正确处理单例实例来避免这种情况

于 2010-09-01T10:05:18.107 回答
11

整个讨论是对大脑时间的巨大、无休止的浪费。99.9% 的时间,单例没有任何显着的设置成本,并且没有任何理由让人为设置来实现非同步保证延迟加载。

这就是你在 Java 中编写 Singleton 的方式:

public class Singleton{
    private Singleton instance = new Singleton();
    private Singleton(){ ... }
    public Singleton getInstance(){ return instance; }
}

更好的是,让它成为一个枚举:

public enum Singleton{
    INSTANCE;
    private Singleton(){ ... }
}
于 2010-09-01T10:39:23.623 回答
7

我不知道它是否坏了,但是由于同步相当昂贵,它并不是最有效的解决方案。更好的方法是使用“Initialization On Demand Holder Idiom”,顾名思义,它会在第一次需要时将单例加载到内存中,从而延迟加载。使用此习惯用法的最大好处是您不需要同步,因为 JLS 确保类加载是串行的。

关于该主题的详细维基百科条目:http ://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

要记住的另一件事是,由于 Spring 和 Guice 等依赖注入框架已经出现,类实例正在由这些容器创建和管理,如果需要,它们会为您提供 Singleton,因此不值得打破你的头脑超过它,除非你想学习背后的模式,这是有用的。另请注意,这些 IOC 容器提供的单例是每个容器实例的单例,但通常每个应用程序都有一个 IOC 容器,因此不会成为问题。

于 2010-09-01T09:59:14.063 回答
6

问题如下:您的 JVM 可能会重新排序您的代码,并且不同线程的字段并不总是相同的。看看这个:http ://www.ibm.com/developerworks/java/library/j-dcl.html 。使用 volatile 关键字应该可以解决这个问题,但在 java 1.5 之前它被破坏了。

大多数情况下,单检查锁定已经足够快了,试试这个:

// single checked locking: working implementation, but slower because it syncs all the time
public static synchronized Singleton getInst() {
    if (instance == null) 
        instance = new Singleton();
    return instance;
}

还可以查看有效的 java,您将在其中找到有关该主题的精彩章节。

总结一下:不要做双重检查锁定,有更好的 idoms。

于 2010-09-01T10:28:19.680 回答
4

Initialization On Demand Holder Idiom,是的,就是这样:

public final class SingletonBean{

    public static SingletonBean getInstance(){
        return InstanceHolder.INSTANCE;
    }

    private SingletonBean(){}

    private static final class InstanceHolder{
        public static final SingletonBean INSTANCE = new SingletonBean();
    }

}

尽管 Joshua Bloch 在Effective Java Chapter 2, Item 3 中也推荐了 Enum 单例模式:

// Enum singleton - the prefered approach
public enum Elvis{
    INSTANCE;
    public void leaveTheBuilding(){ ... }
}
于 2010-09-01T10:06:08.557 回答
2

这不能回答你的问题(其他人已经做过),但我想告诉你我对单例/延迟初始化对象的经验:

我们的代码中有几个单例。有一次我们不得不为一个单例添加一个构造函数参数并且遇到了一个严重的问题,因为这个单例的构造函数是在 getter 上调用的。只有以下可能的解决方案:

  • 为初始化此单例所需的对象提供静态 getter(或另一个单例),
  • 传递对象以将单例初始化为 getter 的参数或
  • 通过传递实例来摆脱单例。

最后,最后的选择是要走的路。现在我们在应用程序启动时初始化所有对象并传递所需的实例(可能作为一个小接口)。我们没有后悔这个决定,因为

  • 一段代码的依赖关系一清二楚,
  • 通过提供所需对象的虚拟实现,我们可以更轻松地测试我们的代码。
于 2010-09-01T10:44:11.077 回答
2

这里的大多数答案对于为什么它被破坏都是正确的,但不正确或提出了可疑的解决方案策略。

如果你真的,真的必须使用单例(在大多数情况下你不应该这样做,因为它破坏了可测试性,将有关如何构造类的逻辑与类的行为结合起来,将使用单例的类与如何获得一个,并导致代码更脆弱)并且关心同步,正确的解决方案是使用静态初始化程序来实例化实例。

private static Singleton instance = createInst();

public static Singleton getInst() {
    return instance ;
}

private static synchronized createInst() {
    return new Singleton(); 
}

Java 语言规范保证静态初始化程序只会在第一次加载类时运行一次,并且以保证的线程安全方式运行。

于 2010-09-01T13:54:38.840 回答