Singleton Pattern

单例模式能够保证类只有一个实例对象。在Java中可以使用多种方式来实现单例模式。

Eager initialization

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SingletonEagerInit {

private static final SingletonEagerInit instance = new SingletonEagerInit();

private SingletonEagerInit () {}

public static SingletonEagerInit getInstance() {
return instance;
}

// other methods

}

该方式在类加载的时候就对实例进行初始化。然后每次调用getInstance都会返回这个实例对象。

该方式是有缺点,如果这个类的对象非常大,在加载该类后却没有调用getInstance方法,即加载了类,但没有使用该类的对象。但是这个对象已经被实例化,会占用一部分内存,如果我们用不着这个对象,这些内存就浪费了。

Lazy initialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingletonLazyInit {

private static SingletonLazyInit instance;

private SingletonLazyInit () {}

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

// other methods

}

延迟初始化的方式能够在真正用到实例对象的时候才对其进行初始化。

对整个方法进行synchronized,避免了多线程下的同步问题,但是对整个方法进行synchronized效率会下降。

double checked locking

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SingletonDoubleChecked {

private static volatile SingletonDoubleChecked instance;

private SingletonDoubleChecked () {}

// effective java : 第71条
// 使用局部变量可以提供性能
public static SingletonDoubleChecked getInstance() {
SingletonDoubleChecked result = instance;
if (result == null) {
synchronized(SingletonDoubleChecked.class) {
result = instance;
if (result == null) {
instance = result = new SingletonDoubleChecked();
}
}
}
return result;
}

// other methods

}

该方法并为整个方法进行synchronized,尽可能的缩小了同步块的范围。

字段instancevolatile修饰是为了可见性,一致性,顺序性,可见性就是指当对一个临界资源进行修改后,其他线程就能马上觉察到该修改,所有线程看到这个变量的值是一致的,而且编译器也不会对其进行指令重排。

getInstance方法中使用局部变量result的原因可以见Effective Java第二版的第71条。

这个变量的作用是确保实例变量只在已经被初始化的情况下读取一次。虽然这不是严格要求,但是可以提升性能。

我的理解是instance这个实例变量是在堆内存中分配的,而局部变量result是在栈中分配的,栈的读取速度要快于堆的读取速度。

如果该单例的类需要实现序列化,那么在加上implements Serializable后,还需要添加readResolve方法。该类的实例域都要声明为transient

1
2
3
private Object readResolve() {
return instance;
}

对与clone方法,可以直接抛出异常。

1
2
3
4
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}

Initialization-on-demand holder idiom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SingletonByHolder {

public static SingletonByHolder getInstance() {
return SingletonHolder.instance;
}

private SingletonByHolder() {}

// other methods

private static class SingletonHolder {
static final SingletonByHolder instance = new SingletonByHolder();
}

}

这种方式也是只有在第一次调用getInstance方法时才进行初始化。在加载SingletonByHolder类时并不会加载SingletonHolder类,所以就不会对instance进行初始化,只有在getInstance方法中调用SingletonHolder.instance才会加载SingletonHolder类,然后进行初始化。

The enum way

1
2
3
4
5
6
7
public enum Singleton {

INSTANCE;

// other methods

}

使用枚举来实现单例模式是被推荐的。

这种方法在功能上与公有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。