java中的单例
我只是读了下面的代码:
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
的单个副本,为什么我们需要这个代码呢?
这个类有一个字段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();
}
这为您节省了内存,直到真正需要单例。 这种策略的缺点是,第一次调用方法可能会看到稍差的响应时间,因为它在返回之前必须初始化obejct。
懒惰与渴望的初始化
if
语句是延迟初始化技术的实现。
更明确的版本如下:
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
被“懒惰地”初始化。 它在第一次需要时才会被初始化。
也可以看看
在线程安全
需要说的是,片段不是线程安全的。 如果有多个线程,那么在某些竞争条件下,可能会多次调用new SingletonObjectDemo()
。
一种解决方案是synchronized getSingletonObject()
方法。 但是,这对所有对getSingletonObject()
调用getSingletonObject()
产生同步开销。 然后使用所谓的双重检查锁定语法来尝试修正这一问题,但在Java中,直到J2SE 5.0在新的内存模型中引入volatile
关键字时,这种习语才真正起作用。
不用说,单身模式的正确执行并不是一件微不足道的事情。
也可以看看
相关问题
有效的Java第二版
以下是这本书在这些主题上要说的话:
项目71:明智地使用延迟初始化
与大多数优化的情况一样,懒惰初始化的最佳建议是“除非需要,否则不要这样做”。 懒惰初始化是一把双刃剑。 它降低了初始化类或创建实例的成本,代价是增加了访问延迟初始化字段的成本。 根据延迟初始化字段的一小部分最终需要初始化,初始化它们的花费是多少,以及每个字段被访问的频率如何,延迟初始化(像许多“优化”实际上会损害性能)。
在多线程的情况下,延迟初始化非常棘手。 如果两个或两个以上的线程共享一个懒惰的初始化字段,那么使用某种形式的同步至关重要,否则会导致严重的错误。
在大多数情况下,正常初始化优于延迟初始化。
项目3:使用私有构造函数或enum
类型强制使用单例属性
截至1.5版本。 还有第三种方法来实施单身人士。 只需使用一个元素创建一个枚举类型。 [...]这种方法在功能上等同于public
字段方法,只是它更简洁,提供了免费的序列化机制,并提供了针对多个实例的铁证保证,即使面对复杂的序列化或基于反射的攻击。
[...]单元枚举类型是实现单例的最好方法。
相关问题
在enum
单例/ Java实现上:
单身模式的优点和选择:
如果我们不使用这部分代码呢? 仍然会有SingletonObjectDemo的单个副本,为什么我们需要这个代码呢?
这个想法是延迟加载单例,即只在实际需要时加载实例。 你为什么想这么做? 那么Bob Lee在Lazy Loading Singletons中总结得非常好:
在生产中,您通常希望急切地加载所有单例,以便尽早发现错误并提前展现性能,但在测试和开发期间,您只需加载绝对需要的内容,以免浪费时间。
但是你显示的实现被破坏,它不是线程安全的,并且两个并发线程实际上可以创建两个实例。 使您的懒加载的单例线程安全的最好方法是使用初始化按需持有者(IODH)成语,它非常简单并且没有同步开销。 引用有效Java,项目71:明智地使用惰性初始化(重点不是我的):
如果您需要在静态字段上使用延迟初始化来实现性能,请使用延迟初始化持有者类成语 。 这个习惯用法(也称为按需初始化持有者类成语)利用了保证一个类在被使用之前不会被初始化[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
在于getField
方法不同步并且只执行字段访问,所以懒惰的初始化几乎不会增加访问的成本。 一个现代的虚拟机将同步字段访问只用于初始化类。 一旦类被初始化,虚拟机将修补代码,以便后续访问该字段不涉及任何测试或同步。
也可以看看
上一篇: Singleton in java
下一篇: Why Java does not allow overriding equals(Object) in an Enum?