单例模式意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1. 惰性初始化的单例模式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 不安全的发布
}
return instance;
}
}
在没有充分同步的情况下就发布一个对象,会导致另外的线程看到一个部分创建对象。
2. 如何安全发布对象?
2.1 线程安全的惰性初始化
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
虽然解决了线程安全问题,但是每个线程调用getInstance()都要加锁。
2.2 主动初始化
public class Singleton {
private static Singleton sInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return sInstance;
}
}
如果对象的初始化很昂贵,那么将它们的初始化延迟到真正需要它们的时刻是很有意义的。
2.3 惰性初始化容器(Lazy Initialization Holder),静态内部类单例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static Singleton sInstance = new Singleton();
}
}
JVM将SingletonHolder类的初始化延迟到真正使用它的时刻。线程第一次调用Singleton.getInstance(),引起SingletonHolder的加载和初始化。
2.4 双检查锁(Double-Checked Locking)
public class Singleton {
private static volatile Singleton sInstance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
synchronized(Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
注意要将sInstance声明为volatile,这样可以避免指令重排序。
根据《Java并发编程实践》一书所述,催生DCL这个技巧的原因是缓慢的无竞争同步和缓慢的JVM启动,然而这些原因随着JVM的发展已经不复存在,这使得DCL的优化效果越来越不明显。
惰性初始化容器的模式提供了同样的好处,而且更易理解。
参考资料
- Java并发编程实践