问题:

如何保证一个类只被实例化一次?
某类对象希望保证只有一个实例,如数据库连接池(减少数据库连接的开销),如软件的全局配置信息(配置共享)等。

期望:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

实现:

思路:获取实例时,如果实例已经存在,则返回,否则创建。

实现-1 (懒汉)

优点:lazy loading,第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,加锁会影响效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
// 使用静态变量记录Singleton唯一的实例
private static Singleton uniqueInstance;
// 构造器声明为私有,只有Singleton类内才可以调用构造器
private Singleton() {}
// getInstance 实例化对象,提供全局访问点
// 使用synchronized解决线程不安全导致多个实例被创建的问题
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
实现-2 (饿汉)

优点:没有加锁,提高执行效率。
缺点:类加载时就初始化,浪费内存。

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
// 基于classloder避免多线程同步问题,类装载的时候就实例化
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
实现-3 (懒汉,DCL,double-checked locking)

采用双锁机制,安全且在多线程情况下能保持高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton(); //非原子操作,用volatile保证不重排序
}
}
}
return singleton;
}
}
实现-4 (静态内部类)

使用classloader实现,同实现-2的区别在于,使用SingletonHolder才会进行实例化,实现了lazy loading。
优点:DCL功能保证,实现简单。
缺点:只适用于静态域,实例域还是用DCL。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
// 只有在getInstance 被调用时,SingletonHolder才会被装载,实现了延迟加载和线程安全
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
实现-5 (枚举,懒汉)

实现单例的最佳方法,lazy loading,线程安全,支持序列化机制,防止反序列化多次实例化。防止反射来调用私有的构造方法。
(枚举实例的创建是线程安全的。)

1
2
3
4
5
6
7
8
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
调用:
Elvis e = Elvis.INSTANCE

其他问题:

  1. 多个class loader时,会有多个实例。要保证多个Class Loader不会装载同一个Singleton
  2. 继承Singleton的子类可能有多实例的问题,杜绝继承Singleton
  3. 序列化会得到一个新的对象,破话了单例性。(序列化会通过反射调用无参数的构造方法创建一个新的对象。)
    • 在反序列化时,会通过反射的方式调用要被反序列化的类的readResolve方法,所以只要在Singleton类中定义readResolve就可以解决该问题:(具体参见序列化源码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}

参考:
单例模式
深入浅出单实例SINGLETON设计模式
单例与序列化的那些事儿
理解Java对象序列化