singleton = new Singleton()这段代码其实不是原子性的操作,它至少分为以下3个步骤:
(1)singleton对象分配内存空间
(2)调用Singleton类的构造函数等,初始化singleton对象
(3)将singleton对象指向分配的内存空间,这步一旦执行了,那singleton对象就不等于null了
这里还需要知道一点,就是有时候JVM会为了优化,而做指令重排序的操作,这里的指令,指的是CPU层面的。
正常情况下,singleton = new Singleton()的步骤是按照1->2->3这种步骤进行的,但是一旦JVM做了指令重排序,那么顺序很可能编程1->3->2,如果是这种顺序,可以发现,在3步骤执行完singleton对象就不等于null,但是它其实还没做步骤二的初始化工作,但是另一个线程进来时发现,singleton不等于null了,就这样把半成品的实例返回去,调用是会报错的。
DCL使用volatile关键字,是为了禁止指令重排序,避免返回还没完成初始化的singleton对象,导致调用报错,也保证了线程的安全。
volatile作用:
- 保证被修饰的变量对所有线程的可见性。
- 禁止指令重排序优化。
public class SingleInstance {
private SingleInstance() {}
private volatile static SingleInstance INSTANCE;
public static SingleInstance getInstance() {
if (INSTANCE == null) {
synchronized (SingleInstance.class) {
if (INSTANCE == null) {
INSTANCE = new SingleInstance();
}
}
}
return INSTANCE;
}
}