【问题标题】:Singleton design pattern broken? [duplicate]单例设计模式被打破? [复制]
【发布时间】:2012-01-11 00:30:04
【问题描述】:

可能重复:
Efficient way to implement singleton pattern in Java

我原以为下面的类是线程安全的单例,但阅读 http://taskinoor.wordpress.com/2011/04/18/singleton_multithreaded/ 似乎不是。

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton ref;

    private ThreadSafeSingleton(){

    }

    public ThreadSafeSingleton getSingletonObject(){

        if(ref == null){
            ref = new ThreadSafeSingleton();
        }   
        return ref;

    }
}

根据文章,唯一真正线程安全的单例是 -

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton ref = new ThreadSafeSingleton();

    private ThreadSafeSingleton(){

    }

    public ThreadSafeSingleton getSingletonObject(){
        return ref;

    }
}

这样对吗?

【问题讨论】:

  • 请检查右侧栏中的“相关”列表(顺便说一下,这与您输入问题标题后出现的列表完全相同)。当然这个问题之前已经被问过无数次了:)
  • 绝对不是唯一的线程安全方式,甚至不是最好的线程安全方式。确实,顶部不是线程安全的,而底部似乎是。
  • 我的理解是,底层解决方案是“最好的”线程安全的单例实现(尽管最好的可能取决于特定的用例)。 Glowcoder,你推荐哪种方式更好?
  • @increment 你如何定义“最佳”?不同的解决方案有不同的权衡。如果您以后只需要该对象,这当然不是昂贵的初始化的最佳选择。
  • @user470184,这不是实现线程安全单例的唯一方法。博客里没有说。有许多特定于平台的解决方案,在博客文章的末尾简要描述了这些解决方案。特别是,如果您使用 J2SE 5.0 或更高版本,则可以将volatile 与 DCLP 一起使用,因此也可以进行延迟初始化。

标签: java design-patterns singleton


【解决方案1】:

这不是唯一的线程安全单例,但这是正确的。另一种方法是同步第一个示例中创建单例实例的代码。但是这样做的问题是您必须同步代码,这可能是也可能不是问题。另一个可能的问题是单例没有延迟初始化。同样,这可能是也可能不是问题,具体取决于架构和要求。还有另一种奇怪的模式可以解决这个问题:http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

【讨论】:

【解决方案2】:

是的,这篇文章是正确的。

在上面的示例中,如果该方法同时被两个函数调用,它们可能都将 ref 视为 null,因为在另一个函数检查之前,它们都没有真正完成创建和分配。

在下面的示例中,ref 在类加载时分配一次,然后任何东西都可以访问它。

【讨论】:

    【解决方案3】:

    是的,没错。

    在第一种情况下,如果两个线程同时调用getSingletonObject(),则存在以两个单例实例结束的风险。

    在第二种情况下,该方法只返回对在类加载期间创建的现有对象的引用,这是由 JVM 以线程安全的方式完成的。

    第二个在线程安全方面比第一个好很多。

    【讨论】:

      【解决方案4】:
      if(ref == null)
      {
          ref = new ThreadSafeSingleton();
      }            
      
      return ref; 
      

      同时运行的两个线程可能会命中if (ref == null)。两个线程可以看到ref 实际上是空的。然后两个线程将创建一个新实例。然后可以给定两个线程单独的实例。现在您有两个不同的“单例”对象实例。

      在这个 sn-p 中没有适当的机制来防止上述竞争条件。

      【讨论】:

        【解决方案5】:

        有几种不同的方法可以实现线程安全的单例对象。静态初始化(底部方法)是其中一种方法。

        您也可以使用static inner classdependency injection、同步getInstance(),甚至是double-checked locking pattern in Java 5+,只要将持有者变量声明为volatile。请参阅Java Concurrency in Practice 了解更多信息。

        其中,如果我必须构建一个实际的单例,我更喜欢第一种方法。否则我喜欢在Guice 中使用范围。

        【讨论】: