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 实现:
关于单例模式的优点和替代方案:
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中的单例的主要内容,如果未能解决你的问题,请参考以下文章