注意点
- lazy loading
- 线程安全
- jvm 创建对象顺序
一、饿汉
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
或
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
- 基于classloder机制避免了多线程的同步问题
- 导致类装载的时机很多,所以有可能达不到lazy loading
二、静态内部类
public class Singleton {
private static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 基于classloder机制避免了多线程的同步问题
- 只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance,实现lazy loading
三、懒汉
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 效率很低,因为99%情况下不需要同步
四、双重检查锁定
public class SafeDoubleCheckedLocking {
private volatile static Instance instance;
public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance();//instance为volatile,现在没问题了
}
}
return instance;
}
}
- 双重检查锁定示例代码的第7行(instance = new Singleton();)创建一个对象。这一行代码可以分解为如下的三行伪代码:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
- 上面三行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的)。2和3之间重排序之后的执行时序如下:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址
//注意,此时对象还没有被初始化!
ctorInstance(memory); //2:初始化对象
- volatile 避免了 2、3的重排
- 通过对比基于volatile的双重检查锁定的方案和静态内部类的方案,我们会发现静态内部类的方案的实现代码更简洁。但基于volatile的双重检查锁定的方案有一个额外的优势:除了可以对静态字段实现延迟初始化外,还可以对实例字段实现延迟初始化。
- 讨论下volatile关键字的必要性,如果没有volatile关键字,问题可能会出在singleton = new Singleton();这句,用伪代码表示
inst = allocat(); // 分配内存
sSingleton = inst; // 赋值
constructor(inst); // 真正执行构造函数
可能会由于虚拟机的优化等导致赋值操作先执行,而构造函数还没完成,导致其他线程访问得到singleton变量不为null,但初始化还未完成,导致程序崩溃。
五、枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
上边四种单例方式都有一个弊端,都可以通过反射调用私有构造方法进行实例化。但通过枚举做单例就不会有这个问题。具体原理参见:http://www.jianshu.com/p/ec811fc70b08 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization