单例模式

这里介绍五种单例模式的实现。

饿汉模式

单例模式主要有五种,先列出饿汉模式,饿汉,顾名思义,就想快点new出对象。想来,只要把静态变量指向需要new的对象,就能在构造方法执行之前生成:

1
2
3
4
5
6
7
8
9
10
11
public class Singleton01 {
private static Singleton01 singleton = new Singleton01();
private Singleton01() {
}
public static Singleton01 newInstance() {
return singleton;
}
}

这种模式的特点是,线程安全,资源浪费和加载慢(可能情形)。

内部类懒加载模式

饿汉模式不是懒加载,取其优点,可以构造一种内部类懒加载模式,比如把一开始的静态字段放在私有静态内部类中,这样可以保证线程安全:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton02 {
private static class Singletonolder {
private static Singleton02 singleton = new Singleton02();
}
private Singleton02() {
}
public static Singleton02 newInstance() {
return Singletonolder.singleton;
}
}

懒汉模式

延迟加载还有另一种形式,被称作懒汉模式。在这种模式中,字段也不和new创建对象的代码捆绑了,为了保证线程安全,将newInstance()用synchronized修饰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton03 {
private static Singleton03 singleton;
private Singleton03() {
}
public synchronized static Singleton03 newInstance() {
if (singleton == null) {
singleton = new Singleton03();
}
return singleton;
}
}

这种方法的缺点是,每次只有一个线程可以执行newInstance(),而实际上很多时候,可以先判断一下实例是否已经存在,已经存在就没必要调用synchronized修饰的方法了,这样可以大大提高程序的运行成本。

双重检查加锁的懒汉模式

承接前面的分析,可以考虑将sychronized放入newInstance()内部,在它之前先判断一下对象是否已经存在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton04 {
private volatile static Singleton04 singleton;
private Singleton04() {
}
public static Singleton04 newInstance() {
if (singleton == null) {
synchronized (Singleton04.class) {
if (singleton == null) {
singleton = new Singleton04(); //
}
}
}
return singleton; //
}
}

newInstance方法中的代码很容易理解,这里要注意的是,singleton = new Singleton04()这一行代码在具体执行中在底层可能出现指令重排的情况,引用赋值和初始化对象在单线程内重排后并不影响结果,但多线程下,新的线程拿到的singleton引用指向的对象可能并没有完成初始化,所以需要添加volatile关键字。

枚举模式

1
2
3
4
5
6
7
public enum Singleton05 {
INSTANCE;
public void leaveTheBuilding() {
}
}

《Effective Java》中在前面章节提到过枚举模式是实现单例的最佳方法,后面章节再次提到过枚举模式,因为它不仅仅简单高效且线程安全,而且在在反序列化的时候,也能保证对象只有一个,不需要额外的处理。