为什么枚举单例是懒加载的?

huangapple go评论75阅读模式
英文:

Why enum singleton is lazy?

问题

我看到了类似于这样的回答,尝试通过评论进行澄清,但对这里的示例感到不满。

也许现在是针对这个特定问题的时候了...

为什么枚举单例模式的实现被称为惰性

public enum EnumLazySingleton {
    INSTANCE;
    EnumLazySingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

它与急切实现有什么不同?

public class BasicEagerSingleton {
    private static final BasicEagerSingleton instance = new BasicEagerSingleton();
    public static BasicEagerSingleton getInstance() {
        return instance;
    }
    private BasicEagerSingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

两者都将在不访问 INSTANCE/getInstance() 的情况下初始化实例 - 例如调用 touchClass()

public class TestSingleton {
    public static void main(String... args) {
        System.out.println("sleeping for 5 sec...");
        System.out.println("touching " + BasicEagerSingleton.class.getSimpleName());
        BasicEagerSingleton.touchClass();
        System.out.println("touching " + EnumLazySingleton.class.getSimpleName());
        EnumLazySingleton.touchClass();
    }
}

输出结果:

sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE

现在,我们可以说两者都是惰性的。那么什么是急切呢?

很清楚,例如“双重检查锁定”方式实际上是惰性的(而且混乱且缓慢)。但如果枚举是惰性的,那么由于不可避免的类加载,任何单例模式都是惰性的 - 实际上,一切都是惰性的。在哪个点上,这种区分将不再有任何意义?

英文:

I saw answers like these, tried to clarify via comments, and was unsatisfied by examples here.

Maybe it's time for this specific question...

Why enum singleton implementation is called lazy?

public enum EnumLazySingleton {
    INSTANCE;
    EnumLazySingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

How it is different from eager implementation?

public class BasicEagerSingleton {
    private static final BasicEagerSingleton instance = new BasicEagerSingleton();
    public static BasicEagerSingleton getInstance() {
        return instance;
    }
    private BasicEagerSingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

Both will init instance without accessing INSTANCE/getInstance() - e.g. call touchClass().

public class TestSingleton {
    public static void main(String... args) {
        System.out.println("sleeping for 5 sec...");
        System.out.println("touching " + BasicEagerSingleton.class.getSimpleName());
        BasicEagerSingleton.touchClass();
        System.out.println("touching " + EnumLazySingleton.class.getSimpleName());
        EnumLazySingleton.touchClass();
    }
}

Output:

sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE

Now, we can say both are lazy. What is eager then?

It is clear how (e.g) "double-checked locking" way is actually lazy (and messy, and slow). But if enum is lazy, then any singleton is lazy due to inevitable class loading - in fact, everything is lazy. At which point will this distinction stop making any sense?

答案1

得分: 4

前两个链接的回答(由 Peter LawreyJoachim Sauer 给出)都同意枚举不是惰性初始化的。第三个链接中的回答在惰性初始化的含义上简直是错误的。

将枚举用作单例的建议源自Josh Bloch的《Effective Java》。值得注意的是,关于枚举单例的章节中没有提到惰性初始化。稍后有一章专门介绍了惰性初始化,同样没有提到枚举。该章节包含两个要点:

  • 如果您需要对静态字段使用惰性初始化来提高性能,请使用惰性初始化持有者类习语。
  • 如果您需要对实例字段使用惰性初始化来提高性能,请使用双重检查习语。

毫无疑问,如果枚举以任何方式进行惰性初始化,它们将成为这个列表上的另一种习语。实际上,它们并没有,尽管对于惰性初始化含义的混淆导致了一些错误的回答,正如提问者所示。

英文:

The first two linked answers (by Peter Lawrey and Joachim Sauer) both agree that enums are not lazily initialized. Answers in the third link are simply wrong about what lazy initialization means.

The recommendation to use enums as singletons originates from Josh Bloch's Effective Java. Notably, the chapter on enum singletons makes no mention of laziness. There is a later chapter dedicated to lazy initialization, that likewise makes no mention of enums. The chapter contains two highlights.

  • If you need to use lazy initialization for performance on a static field, use the lazy initialization holder class idiom.
  • If you need to use lazy initialization for performance on an instance field, use the double-check idiom.

Undoubtedly, enums would be another idiom on this list if they were in any way lazily initialized. In fact they are not, although confusion about the meaning of lazy initialization results in some incorrect answers, as the OP shows.

答案2

得分: 0

以下是翻译好的部分:

你想要下列的赌注:

您正在尝试识别2个“进程”或... "事物"(让我们将这变得更容易理解 - 因为如果我开始说“代码块”,听起来会更困难)...

  • 在某个时候,类加载器将会运行,您想知道在类加载器加载类时将会执行什么**"事物"**。
  • 在另一个时候,调用类上的方法将会导致另一个**"事物"运行/执行,您想要知道哪些,确切地说,(哪些"进程"**)会开始...

以下事实是相关的:

> - 静态初始化程序在类加载器加载类时运行。类加载器只有在运行的代码遇到加载它的需要(因为已调用方法或字段)时,才会加载类,比如:touchClass()
> - 如果**“EITHER”一个类或“OR”一个枚举类型的单例实例具有在类的static部分中初始化的field,它将会在您“触摸”该类时_立即加载_ - 因为类加载器会在加载时运行所有类或枚举的static initializations**。
> - 惰性加载很可能(这是我关于您所询问的内容的“解释”)会在方法调用要求类创建单例实例时发生 - 这可能会在类或枚举的“加载”之后相当长的时间发生。

以下类的示例:

public class LazySingleton
{
    // 在类加载时,此单例将设置为'null'
    private static singleton = null;

    // 这是一个方法,直到被其他代码块(其他“事物”)调用时才会被调用... 当调用“touchClass()”时,不会创建单例实例。
    public static LazySingleton retrieveSingleton()
    {
        if (singleton == null) singleton = new LazySingleton();
        return singleton;
    }

    // 什么也不做... 即使类加载器已将此Java“.class”文件加载到内存中,单例也*不会*被加载。
    public static void touchClass() { }

    private LazySingleton()
    { System.out.println("constructing: LazySingleton"); }
}

另一方面,在这里:

public enum EagerEnum
{
  // 类加载器将会在此“enum”从“.class”文件(在JAR或磁盘上)加载到内存中时运行此构造函数
  MyEnumConstant();

  private EagerEnum()
  { System.out.println("Eager Enum Constructed"); }

  // 这将导致类加载器立即从Java“.class”文件加载此枚举,并且“MyEnumConstant”也必须被加载 - 这意味着构造函数将被调用。
  public static void touchEnum() { }
}

因此,以下代码将产生输出:

LazySingleton.touchClass();            // 不会输出任何内容
EagerEnum.touchClass();                // 输出“Eager Enum Constructed”
LazySingleton.retrieveSingleton();     // 输出“constructing: LazySingleton”
英文:

May I wager the following:

You are trying to identify 2 "processes" or ... "things" (let's make this easy to understand - because if I start saying "Code Blocks", it sounds more difficult)...

  • At some point the class-loader will run, and you would like to know what "things" will be executed when the class-loader loads a class.
  • At another point invoking a method on the class will cause another "thing" to run / execute, and you would like to know which, exactly, (which "processes") would start..

The following facts are relevant:

> - Static initializers are run when the class-loader loads the class. The class-loader will not load the class until the code that is
> running encounters the need to load it (because a method or field has
> been invoked) such as: touchClass()
> - If a singleton instance of EITHER a class, OR an enumerated type has a field that is being initialized in the static
> part of the class it will be loaded as soon as you 'touch' the
> class - because the Class-Loader runs all static initializations for a class or enum on loading.
> - Lazy loading, likely, (And this is my "interpretation" about what you are asking) would happen when a method invokation asks the class
> to create a singleton instance - which could happen quite a bit of
> time after the "loading" of the class or enum.

A class like the following:

public class LazySingleton
{
    // At time of class-loading, this singleton is set to 'null'
    private static singleton = null;

    // This is a method that will not be invoked until it is called by
    // some other code-block (some other "thing")...  When "touchClass()"
    // is called, the singleton instance is not created.
    public static LazySingleton retrieveSingleton()
    {
        if (singleton == null) singleton = new LazySingleton();
        return singleton;
    }

    // DOES NOTHING...  The Singleton is *not* loaded, even though the
    // Class Loader has already loaded this Java ".class" file
    // into memory.
    public static void touchClass() { }

    private LazySingleton()
    { System.out.println("constructing: LazySingleton"); }
}

Here on the other hand:

public enum EagerEnum
{
  // The class loader will run this constructor as soon as this 'enum'
  // is loaded from a '.class' file (in JAR or on disk) into memory
  MyEnumConstant();

  private EagerEnum()
  { System.out.println("Eager Enum Constructed"); }

  // This will cause the Class Loader to Load this enum from the
  // Java ".class" File immediately, and the "MyEnumConstant" will
  // also have to be loaded - meaning the constructor will be called.
  public static void touchEnum() { }
}

So the following code would produce the output

LazySingleton.touchClass();            // Prints nothing
EagerEnum.touchClass();                // Prints "Eager Enum Constructed"
LazySingleton.getSingletonInstance();  // Prints "constructing: LazySingleton

huangapple
  • 本文由 发表于 2020年9月25日 01:53:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/64051855.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定