单例模式应该是应用最广的一个设计模式了,在以往的工作中,也经常遇到单例类,下面做个总结。
一 、为什么要用单例,或者说什么场景下要用单例
1.为了避免产生多个对象消耗过多的资源,例如访问IO和数据库等资源,这时要考虑使用单例模式
2.某种类型的对象应该只有一个,比如我们会写个Cache类,为了保持数据的统一,只能有一个类
3.需要频繁创建和销毁对象时,为了节省性能销毁,要使用单例
二、单例的几种常用写法
单例的写法有很多,饿汉模式,懒汉模式,枚举类方式等等,但是常用的有以下两种
1.DSL(Double Check Lock)双重校验锁方式
public class Singleton {
private static Singleton mInstance;
private Singleton(){}
public static Singleton getInstance(){
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
如果是JDK5,为了避免DCL失效问题(JDK1.5以前,JMM,Java Memory Model即Java内存模型,无法保证汇编指令的正确执行顺序),只需将mInstance的定义改成
private volatile static Singleton mInstance;
感兴趣的同学可以查一下volatile的作用。
所以正确的DCL写法应该如下:
public class Singleton {
private volatile static Singleton mInstance;
private Singleton(){}
public static Singleton getInstance(){
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
这种DCL的方式,资源利用率高,第一次执行getInstance()时,单例对象才会初始化,但是第一次加载时,反应稍慢,由于java内存模型的原因偶尔会失败,这种方式可以在并发场景不是很复杂,或者JDK6或以后版本下使用。
2.静态内部类形式
public class Singleton {
private Singleton(){}
public static final Singleton getInsatance(){
return SingletonHolder.mInstance;
}
private static class SingletonHolder{
private static final Singleton mInstance = new Singleton();
}
}
这种方式同样利用了classloder的机制(类加载机制)来保证初始化instance时只有一个线程,这种方式是Singleton类被装载了,instance不一定被初始化,因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,那么就应该让他延迟加载。
3.枚举单例
public enum Singleton {
INSTANCE;
public void doSomething(){
System.out.println("do sth.");
}
}
写法简单是枚举单例最大的优点,枚举类和普通类一样,也有字段和方法。最重要的是默认枚举实例的创建是线程安全的,而且还能防止反序列化重新创建新的对象,所以在任何情况下它都是一个单例。
这种方式是Effective Java作者Josh Bloch 提倡的方式, 不过由于JDK1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
总结:以上三种方式都可以使用,一般场景用DCL方式就行,用的最多的一般是静态内部类方式,想装逼的可以用枚举单例。