【问题标题】:How to make a singleton class threadsafe?如何使单例类线程安全?
【发布时间】:2009-09-30 07:39:03
【问题描述】:

我正在用 Java 实现一个单例类,以确保创建的类实例不超过一个。

【问题讨论】:

    标签: java class singleton class-design


    【解决方案1】:

    制作单例的最佳方法是什么?使用枚举。

    public enum Singleton {
        INSTANCE;
        public void foo(){ ... }
    }
    
    // Usage:
    Singleton.INSTANCE.foo();
    

    您可以从 VM 获得大量帮助,不仅可以避免双重实例化,还可以帮助您避免反序列化损坏。

    【讨论】:

      【解决方案2】:

      也许最好的方法是对单个实例使用枚举。这具有可序列化和保证单例以防止序列化和反射的额外好处,这是没有“直接”单例实现的(私有的?我有反射,我嘲笑你的访问修饰符!)。实现起来也很简单:

      public enum Singleton {
          INSTANCE;
      
          // fields and methods go here
      }
      

      【讨论】:

        【解决方案3】:
        public class Singleton {
          public static final Singleton INSTANCE = new Singleton();
          private Singleton() { ... }
        }
        

        使用静态实例变量优于内部“Holder”类。如果您愿意,您还可以将静态成员设为私有并提供访问它的方法,但所做的只是向堆栈添加另一个方法调用。

        【讨论】:

        • 这取决于你想要达到的目标——持有者给你一个懒惰的单例,更简单的版本不会给你延迟加载。
        • 是的,确实如此。 Java 在第一次使用类之前不会加载它们。
        • 不完全正确。它在首次引用它们时加载它们,例如在另一个类的方法签名中。
        • 访问器方法通常由 VM 内联。不要选择公共字段而不是访问器来摆脱“堆栈上的方法调用” - 在您的代码中,它看起来像一个方法调用,但实际上,它会是一回事。
        【解决方案4】:

        惰性实例化怎么样:getInstance() 返回单例,如果是第一次调用,则创建它。

         public class MySingleton
         {
             private static MySingleton instance;
        
             private MySingleton()
             {
                 // construct object . . .
             }
        
             // For lazy initialization
             public static synchronized MySingleton getInstance()
             {
                 if (instance==null)
                 {
                     instance = new MySingleton();
                 }
                 return instance;
             }
        
             // Remainder of class definition . . .
         } 
        

        【讨论】:

        • 几乎从不需要延迟实例化,如果是,Java 会延迟加载类,因此在声明中创建实例实际上 延迟实例化 - 没有同步开销(非常少数情况下不够懒惰,而 skaffman 的答案更好)。
        • 另请注意,此实现会同步每次尝试获取对实例的引用。由于大多数代码会执行以下操作: Singleton.getInstance.doSomething(); Singleton.getInstance.doSomethingElse();总体效果是额外的同步严重限制了并发性,在成员访问/变异方面没有线程安全保证。
        • java 中的双重检查锁定被破坏javaworld.com/jw-02-2001/jw-0209-double.html 简而言之,这种保证此类仅实例化一次的方式可能会导致难以发现的并发问题。永远不要使用它。
        • DCL 从 Java 5 开始没有损坏。查看cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,向下滚动到“在新的 Java 内存模型下”。
        【解决方案5】:

        默认情况下,单例不是线程安全的。创建单例的常用方法是使用工厂方法来处理一个实例的创建。这样您就可以控制实例的创建。

        【讨论】:

          【解决方案6】:

          如果您关心并发,您将不​​会使用共享全局状态。

          【讨论】:

          • 如果单例持有来自单一并行化问题的结果的累积,为什么不呢?单例是全局状态的缩影,因此这将是一个公平的使用场景。
          • 只有一个在进程之间共享的实例表示的类有多种用例。一个典型的使用 spring 设计良好的 webapp 可能会有一堆在单例范围内实例化的类,在工作线程之间共享。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多