【问题标题】:java singleton instantiationjava单例实例化
【发布时间】:2011-06-10 00:35:09
【问题描述】:

我找到了三种实例化 Singleton 的方法,但我怀疑它们中的任何一种是否是最好的。我在多线程环境中使用它们并且更喜欢惰性实例化。
示例 1:

private static final ClassName INSTANCE = new ClassName();

public static ClassName getInstance() {
    return INSTANCE;
}

示例 2:

private static class SingletonHolder { 
    public static final ClassName INSTANCE = new ClassName();
}

public static ClassName getInstance() {
    return SingletonHolder.INSTANCE;
}

示例 3:

private static ClassName INSTANCE;

public static synchronized ClassName getInstance()
{
    if (INSTANCE == null)
        INSTANCE = new ClassName();

    return INSTANCE;
}

我使用 ATM 的项目到处都使用示例 2,但我更喜欢示例 3。还有 Enum 版本,但我就是不明白。

这里的问题是 - 在哪些情况下我应该/不应该使用这些变体?虽然我不是在寻找冗长的解释(还有很多其他主题,但最终都变成了争论 IMO),我希望用几句话就能理解。

【问题讨论】:

    标签: java singleton instance


    【解决方案1】:

    在 java 中实现单例最安全、最简单的方法是使用枚举(就像你提到的那样):

    public enum ClassName {
        INSTANCE;
    
        // fields, setters and getters
    }
    

    枚举语义保证只有一个INSTANCE

    如果不使用枚举方法,则必须注意很多方面,例如竞争条件和反射。我一直在破坏一些框架的单例,并滥用它们,因为它们没有正确编写。枚举保证没有人会破坏它。

    【讨论】:

    • 但是我该如何使用它呢?例如,我有这个: public class MyClass { public enum ClassName { INSTANCE; //我是否将所有方法都放在这里而不是... } // 像往常一样放在这里?编辑:该死,cmets 上没有格式?讨厌它!
    • 您在下面编写您的单例字段和方法。然后通过ClassName.INSTANCE.doSomething(foo) 访问它们
    • @jurchiks:是的。在其他地方,您可以调用 ClassName.INSTANCE.methodName() 来使用单例方法(或者可能使用静态导入来摆脱 ClassName.INSTANCE.)部分。
    • 另一个注意事项:如果我理解正确,enum 方法绝对不是懒惰的。
    • @Carl,类加载是懒惰的。如果您加载类但您不希望加载实例,您只会遇到问题。你不能单独加载它们。
    【解决方案2】:

    示例 1:如果您不需要懒惰的 Singleton,请使用。
    示例 2:永远不要使用 - 它会与太多的类混淆。一个只保存一个变量的内部类似乎有点不必要。 示例 3:这是一个懒惰的 Singleton。如果需要,请使用它。

    【讨论】:

      【解决方案3】:

      首先,确保您需要一个单例,并且您要提供对单例的“全局级”访问。我发现在很多情况下,单例的客户不需要知道它是单例。相反,它们只需要在实例化时获得服务。

      因此,无论您如何获得单例(如果有的话),请考虑更改您的类访问该对象的方式。虽然这意味着修改构造函数和更改“分发更改”,但我发现依赖注入框架降低了成本。然后,DI 框架也可以处理单例实例化(例如,Guice 就是这样做的)。

      除此之外。选项 3 是我熟悉的典型且最常见(且线程安全)的版本。选项 1 主要用于非延迟初始化(并不总是可以接受)。没见过2用过。

      【讨论】:

      • 好吧,我要实例化的类包含 2 个可能包含大量数据的 Map 对象,一个 Map 使用键获取值的两个方法(如果它为 null,则填充该值)和两个方法访问数据库(首先填充选定键的值,其次使用现有数据从数据库中插入/更新/删除数据)。这里不算getter/setter。你认为它不应该是单例吗?
      • 我不确定你是否理解了我所建议的核心。假设您将刚才描述的所有内容封装为一个名为 DBLookupService 和 DBLookupServiceImpl 的接口和相应的类。对于使用 LookupService 的类,它来自哪里并不重要——重要的是它们获得了 LookupService 的实例。这也使得测试变得更加容易。
      • 至于它是否应该是单例 - 可能,但您可以考虑使用多个实例的多核优化代码(例如,如果您可以有意义地划分数据)。但我的观点是,您应该尽一切努力避免从使用服务的代码中调用 getInstance()。这将使测试和未来的更改变得更加容易。
      【解决方案4】:

      示例 1 不使用延迟初始化。

      示例 2 和 3 都是惰性的。示例 2 使用 没有同步开销Initialization on demand holder idiom (IODH)。因此它比示例 3 更快。

      在 Effective Java(第 3 项)中,Joshua Bloch 建议单元素枚举类型是实现单例的最佳方式

      但是,如果您不确定枚举类型,请坚持使用 IODH。

      【讨论】:

      • 为什么示例 1 不使用延迟初始化?我认为它正在使用,因为只有一个实例,并且进行了一次初始化。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-29
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      相关资源
      最近更新 更多