【设计模式】-- 单例模式

单例

实现单例模式的思路是:
一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);
当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;
同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

ResourcesManager中的单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package android.app;
public class ResourcesManager {
private static ResourcesManager sResourcesManager;
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
}
return sResourcesManager;
}
}
}

以上是Android SDK中ResourcesManager类使用的单例模式。

懒汉式
1
2
3
4
5
6
7
8
private static Singleton instance;
public synchronized static Singleton getInstance0() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

添加synchronized关键字,是为了避免多线程并行调用getInstance()造成创建多个实例的问题;
劣势:在多线程并行操作时会影响效率,因为每次只有一个线程执行getInstance()方法,其他线程会等待,所以会在多线程同时执行getInstance()时影响效率。

双重检验锁
1
2
3
4
5
6
7
8
9
10
11
12
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}

该种方式是双重检验锁(double checked locking pattern),两次检查instance是否为空,通过在同步代码块内外两次判断,防止多线程并行执行getInstance()创建多个实例。

volatile关键字?

instance = new Singleton();干了些什么?为啥要加volatile关键字?

当new一个对象时候,一定要干的三件事,顺序不一定:

1.给instance分配内存(这永远是第一步);

2.调用Singleton的构造函数,初始化成员变量;

3.将instance对象只想分配的内存空间(注:执行完这一步就不再为null了)

JVM即时编译器会进行指令重排序优化。
也就是说上面第二与第三顺序是不能保证的,最终的执行顺序是1-2-3或者是1-3-2;
如果是1-3-2,则在3执行完毕,2执行之前,被其他线程抢占,这时instance非null但是还没有执行2,
所以该线程会直接返回instance,然后造成错误。
添加volatile关键字就是不让JVM即时编译器进行重排序优化。

static final
1
2
3
4
5
private static final Singleton instance = new Singleton();
public static Singleton getInstance0() {
return instance;
}

优势:线程安全,写法又简单!
劣势:不能传参,且在声明变量时候就加载到内存中。因为instance被声明为static final类型,所以在第一次类加载的时候就初始化到内存中了,而不是调用getInstance()的时候,这就造成了在调用getInstance()时候不能传参了。

静态内部类
1
2
3
4
5
6
7
private static class Singltonhodler {
private static final Singleton SINGLETON = new Singleton();
}
public static final Singleton getInstance1() {
return Singltonhodler.SINGLETON;
}

这种就是为了避免static final field方式不能传参的缺陷

枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
//使用
public static final Singleton getInstance() {
return Singleton1.INSTANCE;
}

比调用getInstance()方法简单。创建枚举默认就是线程安全的,所以不需要担心double checked locking,
而且还能防止反序列化导致重新创建新的对象。

总结

根据自己项目实际情况选择;
Effective Java中推荐使用枚举方式。

Effective Java第三条


参考链接:

http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
Effective Java
维基百科

Fork me on GitHub