0%

单例的几种写法及浅析

在Android中我们常用到单例模式,所以深刻理解单例模式很有必要。在面试中也经常被问到。

单例定义:保证一个类仅有一个实例,并提供一个访问它的全局方法。
UML图

1,饿汉模式

1
2
3
4
5
6
7
8
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}//私有化构造函数

public static Singleton getInstance() {
return singleton;
}
}

饿汉模式是在类的加载时就完成了初始化,所以加载较慢,但获取对象的速度快。是基于类加载机制避免了线程同步问题,但是也不能完全避免多线程问题(或其他的静态方法)导致类被加载。这时候初始化并没有达到懒加载的效果。
2,懒汉模式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}//私有化构造函数

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

这是懒汉加载模式,先申明一个静态对象(也就是先不实例化),在客户端调用的时候才初始化,这样节约了资源,但是初次加载需要实例化,反应稍慢一点,并且不是线程安全的。
3,懒汉模式(线程安全)

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}//私有化构造函数

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

这种方式是线程安全的,但是每次调用getInstance()方法的时候都要进行同步操作,造成不必要的同步开销,所以这种方式不建议使用。
4,双重检查机制(DCL模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private volatile static Singleton singleton = null;//volatile关键字
private Singleton(){}//私有化构造函数

public static Singleton getInstance() {
if (singleton == null) {//为了避免不必要的同步
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

这里在getInstance()方法进行了两次判空,第一次是为了不必要的同步,第二次是在singleton实例为空的时候才创建实例,这里使用了volatile关键字,从这里可以看到DCL模式也是volatile使用的场景之一。
volatile或多或少会影响性能,不过在程序的正确运行面前这点消耗还是值得的。DCL优点是资源利用率高,第一次执行getInstance()时单例对象才被实例化,效率高。缺点是:第一次加载时反应稍慢,在高并发情况下也有一定的缺陷,虽然发生概率小。DCL在一定程度上解决了资源的消耗和多余的同步,线程安全等问题。但是它还是在某些情况会出现失效的问题,也就是DCL失效,在《java并发编程实践》中作者建议我们使用静态内部类单例模式来替代DCL。
5,静态内部类单例模式

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private Singleton(){}//私有化构造函数

public static Singleton getInstance() {
return SingleHolder.sSingleton;
}
private static class SingletonHolder {
private static final Singleton sSingleton = new Singleton();
}
}

第一次加载Singleton类时不会初始化sSingleton,只会在第一次调用getInstance方法的时候虚拟机加载SingletonHolder初始化sSingleton,这样不仅能够确保线程安全也能保证Singleton类的唯一性,所以推荐使用。
6,枚举单例

1
2
3
4
5
6
public enum Singleton {
INSTANCE;
public void doSomething() {

}
}

默认的枚举实例的创建时线程安全的,并且在任何情况下都是单例,上述的几种单例中,有一种情况下会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘中再读回来,从而获得了一个实例。反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述的几个方法示例中如果要杜绝单例对象被反序列化重新生成对象,要加入如下方法:

1
2
3
private Object readResolve () throws ObjectStreamException {
return singleton;
}

枚举单例的优点是简单,但是大部分应用开发很少用到它,因为这样会使程序的可读性变低,不建议用。