顾名思义,单例对象的类必须保证只有一个实例存在,这有利于我们协调系统整体的行为。

例如在Volley框架中,存在一个RequestQueue队列,这个队列中含有线程池,缓存系统,网络请求等,很消耗资源,因此我们最好不要让它构造多个实例。

#使用场景

确保某个类有且只有一个对象的场景,避免多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如:要创建的对象消耗过多的资源,访问IO和数据库等资源等等

##实现

懒汉模式:

1
2
3
4
5
6
7
8
9
10
11
public class Singleton{
private static Singleton instance;
private Singleton(){}

public static Singleton getInstance(){
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}

这是单例模式的一种最简单实现
但其实在实际的使用中,可能就会面临如下的问题:单线程时正常,而遇到多线程时就会出现多个实例了,因此我们需要对其进行优化。

优化方式:

1
2
3
4
5
6
7
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}

但这种方式仍存在问题: 即使instance已经被初始化,每次调用getInstance方法的时候都会进行同步,这样会消耗不必要的资源。
PS: synchronized关键字添加到方法上有点小题大做,也可以更改为只锁定实例的代码,这样就会使用到双重检查锁定(Double Check Lock),而DCL方法虽然在一定程度上解决了资源消耗,多余同步,线程安全等问题,但是在某些情况下还是会出现失效(因为Java编译器允许处理器乱序执行),因此在这里就不做过多的总结了。而这种的实现方式也是一般不建议使用的

##静态内部类单例模式

这种的实现方式也是推荐使用的一种方式。当第一次加载Singleton类时并不会初始化sInstance,只有在第一次调用getInstance方法时才会导致sInstance被初始化,因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化。

由于java的加载特性,会在使用的时候才会动态加载,同时加载的时候会默认保持同步,这也就有了这种延迟加载的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private Singleton() {

}

public static Singleton getInstance() {
return SingletonHolder.sInstance;
}

private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}

##上述方法的共同不足

以上的两种方法都存在一个相同的情况:当它们遇到反序列化时,均会重新创建对象。
通过序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效地获得一个实例。即使构造函数是私有的,反序列化时仍然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。
而上述的创建方法中,如果要杜绝单例对象在被反序列化时重新生成对象,那么在实现序列化方法的类中就必须加入一个readResolve()方法,这个方法可以让开发人员控制对象的反序列化

1
2
3
private Object readResolve() throws ObjectStreamException{
return SingletonHolder.sInstance;
}

这样当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证

##枚举实现单例

枚举有一下的几个优点:

1. 写法简单
2. 默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例

对于枚举来说,就不必关心反序列化时会重新创建一个对象了。

1
2
3
4
5
6
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
//.....
}
}

##总结

我觉得在单例模式中现在最好的方式就是延迟加载的方式创建了吧。枚举方式创建虽然在java中很好,effetive java虽然也推荐,但是并不是特别适合Android,因为在Android中,枚举所产生的消耗是static的两倍,所以我觉得单例模式的话使用延迟加载的方式创建还是不错的,做好反序列化的readResolve()返回值的设定就好了