Singleton Pattern
2017/04/12
Intent
Ensure a class has only one instance, and provide a global point of access to it.
确保一个类只有一个实例,并提供一个全局访问点。[1]
Implements
Eager initialization
public class Singleton {
public static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
说明: 构造函数声明为private是为了防止用new创建类的实例。
Lazy initialization I
只有第一次调用getInstance()
方法才会创建实例。
public final class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Lazy initialization II
Lazy initialization I 的实现在多线程环境下可能会产生多个实例。比如线程A判断instance == null
后在执行new Singleton()
之前,线程B也在判断instance == null
,这样两个线程调用getInstance()
返回的是不同的实例。解决办法是加一个重量级锁,保证同一时刻只有一个线程能进入getInstance()
方法。
public final class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Lazy initialization III
Lazy initialization II 的实现虽然解决了可能创建多个实例的问题,但是当instance已经创建完毕后,之后线程每一次调用getInstance()
获取单例时都需要同步,这会带来性能问题。为了减少同步,我们可以使用double-checked locking
来减少同步代码块的规模。
public final class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // a
synchronized(Singleton.class) { // b
if (instance == null) { // c
instance = new Singleton();
}
}
}
return instance;
}
}
说明:
- 我上面写的这种 dobule-checked locking [3]只适用于java 5+
- 假设多个线程同时调用
getInstance()
方法,首先在 a 处判断instance是否为null,假设大家都判断为null,然后执行到 b 处,只有一个线程能进入同步代码块执行 b 里面的代码,这个线程创建完instance实例离开同步区后,其它线程就可以一个一个的进入同步代码块了,由于volaile保证了instance
的可见性,当其它线程执行到 c 时,会发现instance不为null了,就不会创建新实例。
Lazy initialization IV
Lazy initialization III已经没什么问题了,但是性能还可以再提高一些。我们可以减少对volatile变量的访问。
public final class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
Singleton result = instance;
if (result == null) {
synchronized(Singleton.class) {
result = instance;
if (result == null) {
instance = result = new Singleton();
}
}
}
return result;
}
}
引入result中间变量,是因为大多数时候,instance已经被实例化了,这样代码对instance的访问就只有一次,《effectiv java》的作者说在他的电脑上这样做相对不引入临时变量,提高了25%的性能。
Lazy initialization V
使用 initialize-on-demand holder class [2]
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return LazyHolder.instance;
}
private static class LazyHolder {
private static Singleton instance = new Singleton();
}
}
补充说明:
- 如果我们要延迟加载的是instance filed,考虑使用 dobule-checked locking
- 如果我们要延迟加载的是static filed,考虑使用 initialize-on-demand holder class
Enum Singleton
上面的单利模式的实现可能会被反射或者序列化攻击,我们使用enum singleton
public enum Singleton {
INSTANCE;
}
当我们第一次调用Singleton.INSTANCE
的时候,Singleton就会被加载和初始化,翻译一下就是:
public final class Singleton {
public final static Singleton INSTANCE = new Singleton();
}
这个方法被[4]的作者认为是单例模式最佳实践。