java中的单例

Posted

技术标签:

【中文标题】java中的单例【英文标题】:Singleton in java 【发布时间】:2011-03-17 09:53:47 【问题描述】:

我只是在某处阅读了以下代码:

public class SingletonObjectDemo 

    private static SingletonObjectDemo singletonObject;
    // Note that the constructor is private
    private SingletonObjectDemo() 
        // Optional Code
    
    public static SingletonObjectDemo getSingletonObject() 
        if (singletonObject == null) 
            singletonObject = new SingletonObjectDemo();
       
       return singletonObject;
    

我需要知道这部分的需求是什么:

if (singletonObject == null) 
    singletonObject = new SingletonObjectDemo();

如果我们不使用这部分代码怎么办? SingletonObjectDemo 仍然会有一个副本,那我们为什么需要这个代码呢?

【问题讨论】:

就是这么说的......显然单例模式说构造函数应该是protected。关于继承的东西。尽管用单例继承似乎几乎不可能做好,但这就是模式疯子的想法。就是这么说的。 这不会创建一个单例。如果两个或更多线程调用 getSingletonObject() 方法,则可以创建更多实例,以便您可以向该方法添加 synchronize 关键字。要使其成为单例,您还应该覆盖 Object clone() 方法。 @cHao:我从Dr. Kabutz of JavaSpecialists 学习了一个专家的设计模式课程(推荐!)他说有时一个应用程序需要在各种替代(但相关)单例之间进行选择,因此子类化能力的可取性.应用程序通常会在运行时选择其中一个实现类,可能通过配置文件和class.forName 实例化。 @Enrique:你能解释一下 clone() 的需要吗?谢谢。 @Ishi:最好的用途是抛出 CloneNotSupportedException 或其他东西(因为单例永远不应该是可克隆的——这需要其中两个同时存在——但是 Object 有一个默认的克隆( ) 方法,可以通过扩展类和实现 Cloneable 轻松打开。 【参考方案1】:

关于惰性初始化与急切初始化

if 语句是lazy initialization 技术的实现。

更明确的版本如下:

private boolean firstTime = true;
private Stuff stuff;

public Stuff gimmeStuff() 
   if (firstTime) 
      firstTime = false;
      stuff = new Stuff();
   
   return stuff;

发生的情况是,第一次调用 gimmeStuff() 时,firstTime 将是 true,因此 stuff 将被初始化为 new Stuff()。在随后的调用中,firstTime 将是 false,因此将不再调用 new Stuff()

因此,stuff 被“延迟”初始化。直到第一次需要它时才真正初始化它。

另见

Wikipedia/Lazy initialization Wikipedia/Initialization on demand holder idiom

关于线程安全

需要说的是sn-p不是线程安全的。如果有多个线程,那么在某些race conditionsnew SingletonObjectDemo()可能会被调用多次。

一种解决方案是制作synchronized getSingletonObject() 方法。但是,这确实会在 ALL 调用getSingletonObject() 时产生同步开销。然后使用所谓的double-checked locking 习惯用法来尝试解决这个问题,但是在Java 中,直到J2SE 5.0 并在新的内存模型中引入了volatile 关键字,这个习惯用法才真正起作用。

不用说,正确执行单例模式并非易事。

另见

developerWorks/Java: Double-checked locking and the Singleton pattern -- A comprehensive look at this broken programming idiom Wikipedia/Double-checked locking

相关问题

synchronized block vs synchronized method?

有效的 Java 第 2 版

以下是书中关于这些主题的内容:

第 71 条:明智地使用惰性初始化

与大多数优化一样,延迟初始化的最佳建议是“除非需要,否则不要这样做”。延迟初始化是一把双刃剑。它降低了初始化类或创建实例的成本,但代价是增加了访问延迟初始化字段的成本。取决于延迟初始化的字段最终需要初始化的部分、初始化它们的成本以及每个字段的访问频率,延迟初始化(就像许多“优化”实际上会损害性能)。

在存在多个线程的情况下,延迟初始化很棘手。如果两个或多个线程共享一个延迟初始化的字段,则采用某种形式的同步至关重要,否则可能会导致严重的错误。

在大多数情况下,正常初始化优于延迟初始化。

第 3 项:使用私有构造函数或 enum 类型强制执行单例属性

从 1.5 版开始。还有第三种实现单例的方法。只需使用一个元素创建一个枚举类型。 [...] 这种方法在功能上等同于 public 字段方法,只是它更简洁,免费提供序列化机制,并提供针对多次实例化的铁定保证,即使在复杂的序列化或基于反射的情况下也是如此攻击。

[...] 单元素枚举类型是实现单例的最佳方式。

相关问题

关于enum单例/Java 实现:

Efficient way to implement singleton pattern in Java Java Enum Singleton Comparing Java enum members: == or equals() ? Thread safety in Singleton

关于单例模式的优点和替代方案:

On Design Patterns: When to use the Singleton? Purpose of singletons in programming What is so bad about Singletons Singletons: good design or a crutch? What’s Alternative to Singleton Singleton: How should it be used

【讨论】:

【参考方案2】:

这个类有一个字段SingletonObjectDemo singletonObject 保存单例实例。现在有两种可能的策略 -

1 - 您使用声明对对象进行急切初始化 -

private static SingletonObjectDemo singletonObject = new SingletonObjectDemo();

这将导致你的单例对象在加载类的那一刻被初始化。这种策略的缺点是,如果你有很多单例,它们都将被初始化并占用内存,即使它们还不需要。

2 - 您对对象进行延迟初始化,即在第一次调用 getSingletonObject() 时对其进行初始化 -

// note that this initializes the object to null by default
private static SingletonObjectDemo singletonObject;

...

if (singletonObject == null) 
        singletonObject = new SingletonObjectDemo();
    

这可以节省您的内存,直到真正需要单例为止。这种策略的缺点是该方法的第一次调用可能会看到稍差的响应时间,因为它必须在返回之前初始化对象。

【讨论】:

谢谢!我正是在寻找这个答案。 还想补充一点,正如其他人指出的那样,如果在多线程中运行代码,您应该使用第二种策略使getSingletonObject() 方法synchronized 使其成为线程安全的-线程环境。如果它不是同步的,并且两个线程同时调用该方法,而对象仍然为空,则两个线程最终都可能各自创建一个新实例。 为什么类会在需要之前被加载? 类加载到 jvm 的时间取决于 jvm 的实现。规范本身并未指定何时加载类。这意味着几乎不可能准确预测何时加载类。我和我认为大多数人都假设性能最差,并且更喜欢第二种选择。这样,我们可以保证对象在我们需要它之前不会消耗内存。【参考方案3】:

如果我们不使用这部分代码怎么办?仍然会有一个 SingletonObjectDemo 的副本,那我们为什么需要这段代码呢?

想法是延迟加载单例,即仅在实际需要时加载实例。你为什么想这么做?好吧,Bob Lee 在Lazy Loading Singletons 中很好地总结了这一点:

在生产环境中,您通常希望立即加载所有单例,以便尽早发现错误并预先处理任何性能问题,但在测试和开发期间,您只想加载绝对需要的内容,以免浪费时间.

但是您展示的实现已损坏,它不是线程安全的,两个并发线程实际上可以创建两个实例。使延迟加载的单例线程安全的最佳方法是使用Initialization on Demand Holder (IODH) idiom,它非常简单并且具有同步开销。引用 Effective Java,第 71 条:明智地使用惰性初始化(重点不是我的):

如果您需要使用延迟初始化来提高性能 静态字段,使用 lazy 初始化持有者类习语。 这个成语(也称为 initialize-on-demand holder class idiom) 利用以下保证: 类将不会被初始化,直到它 使用[JLS,12.4.1]。这是怎么回事 看起来:

// Lazy initialization holder class idiom for static fields
private static class FieldHolder 
    static final FieldType field = computeFieldValue();

static FieldType getField()  return FieldHolder.field; 

getField 方法被调用时 第一次,它读到 FieldHolder.field 第一个 时间,导致FieldHolder 类 进行初始化。这个的美 成语是 getField 方法是 不同步,只执行 字段访问,所以延迟初始化 几乎没有增加成本 的访问。现代虚拟机将 仅将字段访问同步到 初始化类。一旦上课 初始化后,虚拟机将修补 代码以便后续访问 字段不涉及任何测试或 同步。

另见

第 66 项:同步对共享可变数据的访问 - Effective Java 2nd edition 第 71 项:明智地使用延迟初始化 - Effective Java 2nd edition Does the new memory model fix the "double-checked locking" problem?

【讨论】:

【参考方案4】:

这两行检查是否创建了唯一的单例,如果没有,它们将创建单例实例。如果该实例确实存在,则什么都不做并返回。单例实例是在第一次按需创建时创建的,而不是在应用程序初始化时创建。

请注意,您的代码包含 race-condition 错误。当2个线程同时进入时,单例对象可以被分配两次。这可以通过同步方法来解决,如下所示:

public static synchronized SingletonObjectDemo getSingletonObject() 
    if (singletonObject == null) 
        singletonObject = new SingletonObjectDemo();
    
    return singletonObject;

顺便说一句,回到你的问题,这行:

private static SingletonObjectDemo singletonObject;

声明一个静态引用,但它不会实际分配实例,Java 编译器将引用设置为null

【讨论】:

【参考方案5】:

在请求之前,给定的类不会创建对象的第一个实例。 private static 字段是 null 直到第一个请求,然后对象的一个​​实例被构造并存储在那里。后续请求返回相同的对象。

如果您删除这两行代码,您将永远不会真正创建初始实例,因此您将始终返回 null

【讨论】:

【参考方案6】:

这段代码

负责创建第一个对象 防止创建另一个

【讨论】:

【参考方案7】:

首先将singletonObject 设置为null。单例背后的想法是在有人调用 getSingletonObject() 时初始化该对象。如果您不在该部分调用构造函数,则变量将始终为空。

【讨论】:

以上是关于java中的单例的主要内容,如果未能解决你的问题,请参考以下文章

Java中的单例模式

Java中的单例模式

java java中的单例声明。

java中的单例模式

Java中的单例模式

Java中的单例模式和静态类有啥区别? [复制]