【问题标题】:Double checked locking pattern: Broken or not?双重检查锁定模式:损坏与否?
【发布时间】:2010-09-01 09:48:36
【问题描述】:

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

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

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

【问题讨论】:

  • 您可以通过使用 DI/IOC 容器并允许容器控制对象的生命周期而不是将此类逻辑嵌入到对象本身中来完全避免这个问题......不是答案,而是一些东西思考。
  • 问题中发布的代码是否甚至算作双重检查锁定的示例?锁被检查一次。
  • @matt 我在这里看到 if(instance==null) 两次

标签: java concurrency singleton double-checked-locking


【解决方案1】:

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

  1. 线程 A 注意到该值为 未初始化,因此它获得 锁定并开始初始化 价值。
  2. 编译器生成的代码是允许的 将共享变量更新为 指向一个部分构造的 A 完成之前的对象 执行初始化。
  3. 线程 B 注意到共享的 变量已被初始化(左右 它出现),并返回其值。 因为线程 B 相信值 已经初始化,它没有 获取锁。如果 B 使用 对象之前的所有 由 A 完成的初始化由 B 程序可能会崩溃。

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

【讨论】:

  • +1 - 指出 Java 中双重检查锁定模式问题的唯一答案!
  • Volatile 还是会带来一些问题,看看我的回答。
  • 易失性或延迟加载都不是正确的方法,而是使用静态初始化器
【解决方案2】:

整个讨论是对大脑时间的无限浪费。 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(){ ... }
}

【讨论】:

  • 你可能还想让类成为最终的(尽管私有构造函数朝那个方向发展)
  • +1,双重检查锁定是所有过早的纳米优化之母
  • 对于枚举构造函数,private 是多余的。编译器会将其设为私有。它写在规范中:“在枚举声明中,没有访问修饰符的构造函数声明是私有的。”见docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.2
【解决方案3】:

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

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

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

【讨论】:

  • 仅同步创建实例
  • 不错的成语。 (无论如何,这个问题仍然没有答案)。我应该补充一点,更新的 JVM 的同步成本更低。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

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

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

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

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

      【讨论】:

      • Singleton 模式确实被过度使用,并且通常是设计不佳的标志
      【解决方案7】:

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

      如果你真的,真的必须使用单例(在大多数情况下你不应该,因为它破坏了可测试性,将有关如何构造类的逻辑与类的行为,乱扔使用单例的类知道如何获得一个,并导致更脆弱的代码)并且关心同步,正确的解决方案是使用a static initializer来实例化实例。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多