【问题标题】:Is this singleton lazy initializated?这个单例延迟初始化了吗?
【发布时间】:2016-08-29 14:11:07
【问题描述】:

我有这样的代码:

class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
        System.out.println("Singleton constructed.");
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

}

当我们没有 getInstance 的任何其他静态方法时,这个单例延迟初始化了吗?据我所知,类仅在某些情况下被初始化,例如:

  1. 使用 new() 关键字或使用创建类的实例 使用 class.forName() 进行反射,这可能会抛出 Java 中的 ClassNotFoundException。
  2. 调用了 Class 的静态方法。
  3. 已分配静态字段 Class。
  4. 使用了类的静态字段,它不是常量变量。
  5. 如果 Class 是顶级类,并且执行词法嵌套在类中的断言语句。

(来源:http://javarevisited.blogspot.com/2012/07/when-class-loading-initialization-java-example.html#ixzz4IjIe2Rf5

所以当唯一的静态方法是 getInstance 并且构造函数是私有的时,除了使用 getInstance 方法(除了反射)之外,不可能以任何其他方式初始化 Singleton 类。所以这个对象只在我们需要的时候才被创建,所以它是一个惰性初始化,对吧?或者我错过了什么?

【问题讨论】:

    标签: java singleton lazy-initialization


    【解决方案1】:

    您提到了静态方法和私有构造函数。添加另一个静态字段,例如:

    static int NUMBER = 13;
    

    在你的 Singleton 类中。而在其他类的main方法中:

    System.out.println(Singleton.NUMBER);
    

    然后,您将看到您的 Singleton 没有延迟初始化。

    但是,当您的字段是静态 FINAL 时:

    static final int NUMBER = 13;
    

    单例是惰性初始化的。

    此外,当您在 Singleton 类中添加静态和非静态初始化块时:

    {
        System.out.println("non-static");
    }
    
    static {
        System.out.println("static");
    }
    

    顺序是:非静态,私有构造函数,然后是静态的,因为您将对象实例化为静态字段的值。所以,这很棘手:)

    总而言之,在某些情况下,您的 Singleton 可能会被视为惰性初始化,但一般情况下并非如此。

    【讨论】:

      【解决方案2】:

      它是立即初始化的,所以不,不是。通常,延迟初始化意味着您在尝试实际检索它但该字段尚未初始化时对其进行初始化。

      延迟初始化与类初始化无关——它与包含在其中的字段有关。在您的情况下,一旦加载类,该字段将立即初始化。

      您的示例适合使用延迟初始化:

      class Singleton {
          private static Singleton INSTANCE;
      
          private Singleton() {
              System.out.println("Singleton constructed.");
          }
      
          public static Singleton getInstance() {
              if(INSTANCE == null) {
                  INSTANCE = new Singleton();
              }
              return INSTANCE;
          }
      }
      

      这只会在实际请求时构造单例,而不仅仅是在 Singleton 类加载到内存中时。在您的情况下,如果它被加载到内存中,那么它的 static 字段将被初始化(包括您的单例字段)。

      【讨论】:

      • 是的。但是什么时候加载类?不仅当我们使用getInstance 方法的时候?
      • @tdudzik 否。例如,如果某些其他类具有接受 Singleton 作为参数或返回它的方法或类具有类型为 Singleton 的字段,它将被加载
      • 你能给我一个例子,当我们不使用Singleton.getInstance() 方法就可以看到Singleton constructed 时?因为我完全不明白。当我使用Singleton 类型作为参数时,实际上加载了Singleton 类,但仍然没有调用构造函数。
      【解决方案3】:

      编辑:我错了。一个类不会仅仅通过引用来加载,如下所示:

      System.out.println(Singleton.class); 
      

      类加载器会在创建实例、引用静态成员或以编程方式加载后立即加载:

      Class<?> clazz = Class.forName("Singleton"); // fully qualified classname
      

      上面的语句导致类被加载,所有的静态成员和块都会按照在类中出现的顺序进行处理。在您的示例类中,这将导致初始化 INSTANCE 变量(并将消息打印到 System.out)。这证明您的方法不能保证延迟加载。

      实现延迟加载单例的更好方法是使用单例持有者模式:

      class Singleton {
      
          private static class Holder {
              private static final Singleton INSTANCE = new Singleton();
          }
      
          private Singleton() {
              // hide constructor
          }
      
          public static Singleton getInstance() {
              return Holder.INSTANCE;
          }
      
      }
      

      【讨论】:

      • 那么为什么当我写System.out.println(Singleton.class); 时它没有打印Singleton constructed.
      【解决方案4】:

      不,它不是惰性初始化的。 懒惰的意思是第一次打电话或从不。在您的情况下,它不是第一次通话。

      您可以将延迟初始化问题传递给SingletonHolder 类。它将在第一次getInstance 调用时被初始化。

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

      【讨论】:

      • 但是如何证明它不是惰性初始化的呢?
      • 懒惰的意思是第一次打电话或从不。在您的情况下,它不是第一次通话
      【解决方案5】:

      如果您使用的是 .NET 4(或更高版本),John Skeet 建议您执行以下操作:

      您可以使用 System.Lazy 类型使惰性变得非常简单。您需要做的就是将委托传递给调用 Singleton 构造函数的构造函数 - 使用 lambda 表达式最容易完成。

      public sealed class Singleton
      {
          private static readonly Lazy<Singleton> lazy = new Lazy<Singleton> (() => new 
                                  Singleton());
      
          public static Singleton Instance { get { return lazy.Value; } }
      
          private Singleton()
          {
          }
       }
      

      它很简单并且性能很好。如果需要,它还允许您使用 IsValueCreated 属性检查实例是否已创建。

      上面的代码隐式使用 LazyThreadSafetyMode.ExecutionAndPublication 作为 Lazy 的线程安全模式。根据您的要求,您可能希望尝试其他模式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-10
        • 2011-02-01
        • 2019-09-17
        • 1970-01-01
        相关资源
        最近更新 更多