Fork me on GitHub

单例设计模式

单例模式比较常见,本文对单例模式的写法进行一个总结。

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {
private static Singleton instance=null;
private Singleton(){

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

懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟.但是存在一个问题就是线程不安全的。例如:当两个线程同时运行到判断instance是否为空的if语句,并且instance确实没有创建好时,那么两个线程都会创建一个实例。

饿汉式

1
2
3
4
5
6
7
8
public class Singleton {  
private Singleton() {}
private static final Singleton instance = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return instance;
}
}

饿汉式本身就是线程安全的,饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。缺点:没有lazy loading的效果,从而降低内存的使用率

Double Check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static volatile Singleton instance = null;

private Singleton(){}

public static Singleton getSingleton(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
  • 注意:假设没有关键字volatile的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行instance = new Instance(),该构造方法是一个非原子操作,编译后生成多条字节码指令,由于JAVA的指令重排序,可能会先执行instance的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后instance便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程B进入,就会看到一个不为空的但是不完整(没有完成初始化)的Instance对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。

  • 禁止指令重排优化这条语义直到jdk1.5以后才能正确工作。此前的JDK中即使将变量声明为volatile也无法完全避免重排序所导致的问题。所以,在jdk1.5版本前,双重检查锁形式的单例模式是无法保证线程安全的。

静态内部类

由于类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载,所以引出了这种写法。

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

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

优点:达到了lazy loading的效果,即按需创建实例

枚举写法

这是Effective Java推崇的一种优雅的写法

1
2
3
4
5
6
7
8
9
10
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}

枚举写法比较优雅但是不是任何场景都适用,虽然Effective Java中推荐使用,但是在Android平台上却是不被推荐的。在这篇Android Training中明确指出:

  • Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.

总结

不管采取哪种方式,单例模式必不可忘的三大要素:

  • 线程安全
  • 延迟加载
  • 序列化与反序列化安全