有些对象我们只需要一个,比如:线程池、缓存、对话框、处理偏置爱好设置和注册表的对象、
日志对象、充当打印机、显卡等设备驱动程序的对象。
事实上,这些对象只能有一个实例,如果制造出更多的实例,就会导致许多问题产生,
例如:程序的行为异常、资源使用过量、或者是不一致的结果。
单件模式可以确保只有一个实例被创建。
单件模式也给我们一个全局访问点,和全局变量一样,不同的是全局变量会在程序一开始就被创建出来,
如果这个对象非常消耗资源,而程序在这次的执行过程中又一直没有用到它就是浪费,而利用单例模式,
我们可以在需要的时候才创建对象。
1 Eager initialization
如果应用程序总是需要创建并使用单间实例,或者在创建和运行时方面的负担不会太繁重,你可以使用
急切(eagerly)创建此单件,如下所示:
public class EagerSingleton {
private static volatile EagerSingleton instance = new EagerSingleton();
// private constructor
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
急切实例化能很好地工作,但是它有一个弊端。实例会被马上创建而不管是否程序在运行时是否需要用到它,
如果这是一个相对大的对象那么这就是一种更浪费。
下面的方法(Lazy initialization)能很好地解决这个问题。
2 Lazy initialization
在程序设计中,延迟实例化是一种延迟——创建对象、计算数值、还有其他开销大的操作知道我们第一次真的需要它的时候
的策略,在单件模式中,它限制实例的创建直到程序第一次请求它的时候。让我们来看看代码实现
public final class LazySingleton {
private static volatile LazySingleton instance = null;
// private constructor
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
}
一旦方法LazySingleton.getInstance()被调用,上面的逻辑会先检查实例存在与否,
如果不存在(instance == null)就创建一个实例,然后返回这个实例;
如果实例已经存在就返回这个实例。
不过在多线程中使用这个方法会遇到问题
考虑两个线程以下列顺序调用了LazySingleton.getInstance()
这会导致在我们的程序中产生两个LazySingleton对象,这就会导致程序出错。
用双重检查(double-checked locking)可以解决这个错误.
首先检查实例是否已经创建,如果尚未创建才进入同步区块,并且会再次检查实例是否已经创建,实现代码:
public class EagerSingleton {
private static volatile EagerSingleton instance = null;
// private constructor
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
if (instance == null) {
synchronized (EagerSingleton.class) {
// Double check
if (instance == null) {
instance = new EagerSingleton();
}
}
}
return instance;
}
}
以上就是一个单件模式的正确的实现。
注意:
- 这里确保使用了”volatile”关键字,否则会导致一个(out of order write error),
会使得对这个实例的引用在对象还没有被创建之间返回(JVM 仅仅分配了相应内存但是构造方法还没有被调用执行)。
在这种情形下,你的另一个线程使用这个对象的实例引用时,会出现null pointer exception甚至导致整个程序崩溃。 - 同时,要注意双重检查不适用于java-1.4及更早版本,这些版本的JVM对于volatile关键字的实现会导致双重检查失效。