双重检查锁定模式:是否破碎?

为什么模式被认为是破碎的? 它看起来很好吗? 有任何想法吗?

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

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

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

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

整个讨论是一个巨大的,无休止的脑时间浪费。 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(){ ... } } 

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

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

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

问题如下:您的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。

初始化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第2章第3项中推荐了Enum单例模式:

 // Enum singleton - the prefered approach public enum Elvis{ INSTANCE; public void leaveTheBuilding(){ ... } } 

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

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

  • 为初始化这个单例所需的对象提供静态getter(或其他单例),
  • 传递对象以初始化单例作为getter的参数或
  • 通过传递实例摆脱单身人士。

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

  • 一段代码的依赖关系非常明确,
  • 我们可以通过提供所需对象的虚拟实现来更轻松地测试我们的代码。

这里的大多数答案都是正确的,为什么它被打破,但是不正确或暗示可疑的解决方案策略。

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

 private static Singleton instance = createInst(); public static Singleton getInst() { return instance ; } private static synchronized createInst() { return new Singleton(); } 

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