new是个多指令的非原子操作- 使用了
synchronized包裹的块代码是块与块之间的最终行为表现是有序确定的,但在线程内部是否存在指令重排(无序)是不确定的 - 第一个
if并未在synchronized里面,所以另一个线程可能拿到一个因synchronized块内部的指令重排导致的未完全初始化的对象(先分配地址,后初始化,但外部线程看已经不为 null 了)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Singleton {
private static /*volatile*/ Singleton INSTANCE;
public static Singleton getInstance() {
// 从线程 B 的角度看 INSTANCE 可能已经不为 null 了,但不知道它是否初始化完全
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
// 线程 A 执行到这个地方的时候
// 线程 B 可能在第一个 if 那里已经可以看到 INSTANCE 不为 null,但 INSTANCE 可能还只是部分初始化
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
所以需要 volatile 避免指令重排造成的问题,需要特殊说明下,这里 volatile 的禁止指令重排,并不是禁止 new Singleton() 里的指令重排,而是禁止对 INSTANCE 的读取和写入的整体操作的指令重排,保证在 INSTANCE 初始化完(写操作)以前,不能进行读操作。