-
软件设计模式中的单列模式,是一个既简单又复杂的模式。当你一开始学它的时候,看到的也许是这样的一段代
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
}
}在单线程的环境下,这段代码并没有什么问题,如果是处在多线程的环境之中,那就不行了。
举个例子:如果两个(或更多)线程,A 和 B 同时进入到 “if (instance == null)”,它们同时看到 “instance”是 null,然后执行 “instance = new Singleton()”,那么 “instance”就会被初始化了两次, A 和 B 同时就创造了两个不同的 Singleton,然后你的程序就会出现问题。
下面我们来看看一些解决方法。
1. 最简单的就是使用 eager initialization:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE ;
}
}当 Singleton 被加载的时候,INSTANCE 就会先被初始化,然后可以安全地被其他的进程所使用。使用这个方法有一点要注意的,就是如果 Singleton 被加载后 INSTANCE 不被使用,而且 INSTANCE 还携带着一些资源,那么就有可能会影响到程序的性能。
达成 eager initialization 的另一个方法就是使用 Enum,效果一样:
public enum Singleton {
INSTANCE;
}2. 另一个方法,就是使用 double-checked locking:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(this) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}Double-checked locking 只能使用在 JDK 1.5 或以上,还有 instance 前必须带有 volatile 关键字。
当进程 A 进到第一个“if (instance == null)”,它看到 instance 还没被初始化,所以会执行 “synchronized(this)”把代码锁定。这个时候进程 B 也许也到了第一个 if 里面,但是无法继续前进,必须先让 A 完成。当 A 把 instance 初始化之后解锁,B 继续前进,但是当它进入第二个 “if (instance == null)”的时候,看到 instance 已经被初始化了,所以就会直接推出。这样 instance 在任何情况下都只会被初始化一次。
使用“synchronized”这个关键字对代码进行锁定,会对程序的性能有所影响,所以我们把它放到了第一个 if 里面,这样当 instance 被初始化了以后,就不需要再次进入锁定状态,也就不会影响到程序的性能。
3. 最后介绍的方法,叫做 initialization on demand holder,原作者是 Bill Pugh:
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}这个方法使用了一个 static 类(SingletonHolder)来容纳 INSTANCE,只有在第一次调用 getInstance() 的时候,SingletonHolder 里面的 INSTANCE 才会被初始化,也就是说用其它的方法来加载 Singleton 的时候,INSTANCE 是不会被初始化的,这样就保持了 lazy initialization 和线程安全。
网络上还有很多关于这方面的资料,这篇文章算是对它们的作出的一个总结吧。
