"乙"处对象实例化过程是非原子的,共分为3步:1.在内存中开辟一片内存区域 2.在这片内存区域中执行构造函数,实例化对象 3.instance引用指向这片内存区域。
假设instance变量没有用volatile关键字修饰,A、B两线程并发访问DCL.class的getInstance()方法,A先执行(时间上)。当线程A执行到"乙"处,上述3步操作可能被指令重排序为1->3->2,当线程执行了"3",还未执行"2"或"2"未完全执行完,此时 instance 不再是null(instance所指向区域已分配内存)。若此时轮到了线程B的时间片,B在"甲"处判断instance非空,就将instance返回了,而此时instance所指向的对象并未实例化或并未完全实例化,必将发生错误。
如果instance变量用volatile修饰,有两层效果:
-
禁止指令重排序优化:即不会出现指令执行顺序为1->3->2的情况,只能是1->2->3。当线程B执行到"甲"处,instance要么为null,要么指向已完全初始化了的对象(内存区域)。
-
保证instance变量的可见性(此部分内容涉及Java内存模型JMM,不熟悉的同学可以先去学习一下JMM):即当线程A成功初始化实例后,会将实例从线程A工作内存区域刷新到主存中,同时将其他线程(如线程B)工作内存区域中的instance无效化,使得其他线程只能从主存中获取instance对象。
所以,DCL中的实例变量有必要用volatile修饰。