volatile 是保证了可见性还是有序性?
有序性:是因为 instance = new Singleton(); 不是原子操作。编译器存在指令重排,从而存在线程1 创建实例后(初始化未完成),线程2 判断对象不为空,但实际对象扔为空,造成错误。
可见性:是因为线程1 创建实例后还只存在自己线程的工作内存,未更新到主存。线程 2 判断对象为空,创建实例,从而存在多实例错误。
看题目描述,其实题主已经清楚了volatile的禁止指令重排序、保证内存可见性的作用。只是想知道在单例模式的双重检查锁定中volatile的作用而已。
个人观点是,两种作用都有:
volatile对singleton的创建过程的重要性:
-
禁止指令重排序(有序性)
实例化一个对象其实可以分为三个步骤:
(1)分配内存空间。
(2)初始化对象。
(3)将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
(1)分配内存空间。
(2)将内存空间的地址赋值给对应的引用。
(3)初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果(如题目的描述,这里就是因为 instance = new Singleton(); 不是原子操作,编译器存在指令重排,从而存在线程1 创建实例后(初始化未完成),线程2 判断对象不为空后对其操作,但实际对象仍为空,造成错误)。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量,volatile的禁止重排序保证了操作的有序性。
-
Singleton对象的内存可见性
这里由于synchronized锁的是Singleton.class对象,而不是Singleton对象,所以synchronized只能保证Singleton.class对象的内存可见性,但并不能保证Singleton对象的内存可见性;这里用volatile声明Singleton,可以保证Singleton对象的内存可见性。这一点作用也是非常重要的(如题目的描述,避免因为线程1 创建实例后还只存在自己线程的工作内存,未更新到主存。线程 2 判断对象为空,创建实例,从而存在多实例错误)。
转载请注明:学时网 » java 单例模式中双重检查锁定 volatile 的作用?