Little about Singleton Pattern

Singleton status is wether exists, and it only has one instance if it exists.

Article Language: Chinese
Example Language: Java
Reading Time: 5min

其实逻辑很简单, 由类自己决定什么时候要生成一个 instance, 以及确认是否已经生成了一个实例.
提供一个全局的访问点(调用构造函数的方法).

  • 外部不能够调用构造函数 ––> 构造函数私有化( private)
  • 由内部生成一个 instance ––> 实现一个可以调用构造函数的方法.
  • 类需要确认是否已经生成了一个 instance ––> instance == null ? create : instance;

这几个逻辑就可以实现一个简(qi)单(gai)版的单例模式的类了. 代码如下,

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

//private constructor
private Singleton() {/*initialization*/}

//static method for creating and visiting instance from outside
public static Singleton getInstance() {
//whether instance exists
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

然鹅上面的例子不能够解决线程安全问题, 在多线程的环境内, 假设线程 A 和线程 B 同时访问这个类, 当线程 A 访问的时候实例还没有被创建, 而当线程 B 访问的时候 A 已经访问过了, 但是实例并没有被创建, 所以 B 访问的时候 instance 仍然是 null, 那么就会出现 instance 重复创建的问题. 解决的方法也是简单粗暴 ––– 直接加锁, 代码如下,

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private static Singleton instance;

private Singleton(){/*initialization*/}

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

然鹅, 这种方法也有它的问题, 它并不高效. 因为只有在创建的时候才需要 mutual exclusion 来确保不会产生多个实例. 开锁解锁的过程实在麻烦. 于是, 双重锁定出现了.

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

private Singleton(){/*initialization*/}

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

而这个版本的双重锁定依然会产生问题, 原理其实和第一个差不多, 如果 A 在创建的过程中被 B 看到了, 那么 B 会认为 instance 已经被 A 创建好了(可是有可能并没有完成初始化, 只是被分配了内存), 那么直接调用这个实例, 会导致程序崩溃. 修改的方法依然很简单 (简单的东西多半坑多….). 使用 volatile 关键字. 代码如下,

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

private Singleton(){/*initialization*/}

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

这样就能够确保每一个读和写的确定性了, 因为 volatile 确保了它所指向的变量的读操作一定发生在写操作的后面(writing precedes reading). 这样我们就不用让线程每次都枷锁, 而是只有在实例未被创建的时候才做加锁处理, 并且确保实例的确定性.

另一种确保线程安全的方法是, 使用静态初始化(static final). 一加载就初始化. 这样就不需要再考虑其他问题, 直接拿来用就可以了. 代码如下,

1
2
3
4
5
6
7
8
9
10

public class Singleton {
private static final Singleton instance = new Singleton();

private Singleton(){/*initialization*/}

public static Singleton getInstance() {
return instance;
}
}

而这种方法的缺陷在于, 提前占用资源. 并且, 如果在初始化的时候需要设置参数, 那么这种方法无法被应用. 因为 final 域一旦创建不能更改.
最后也是最简单高效的方法应该就是枚举了.

This approach is similar to the public field approach, but it is more concise, provides the serialization machinery for free, and provides and ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. … but a single-element enum type is often the best way to implement a singleton. [1]

Enum 确保线程安全, 不需要担心锁的问题, 并且能够防止序列化或者反射机制导致重新创建新的对象.
代码简洁的让人怀疑人生…

1
2
3
public enum Singleton {
INSTANCE;
}

[1]: Bloch, Joshua. Effective Java (3rd Edition): Joshua Bloch: 9780134685991 … www.bing.com/cr?IG=5B8E21E3EB364911A45C4D01A0F4314B&CID=066CBDC1AC5B61F50EE1B616ADF46080&rd=1&h=v1tkqRWZ4_Dmqdw9V5prH4iBztvXb3p4O5mt9M6yErY&v=1&r=https://www.amazon.com/Effective-Java-3rd-Joshua-Bloch/dp/0134685997&p=DevEx.LB.1,5081.1.