DCL 单例模式是否需要volatile?

Posted LuckyWangxs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DCL 单例模式是否需要volatile?相关的知识,希望对你有一定的参考价值。

1. volatile的作用

        volatile只能用来修饰成员变量,它有两大特性:可见性、有序性,此处的有序性区别于synchornized的有序性。synchornized的有序性指的是多个线程要有序地执行临界区的代码,而volatile的有序性指的是指令有序性(指令不可重排)。
什么是指令重排序?
        指令重排序是指在运行程序时,处理器为了优化性能,代码的执行顺序可能不会按代码的顺序执行,比如前后两个赋值指令,第一条需要去磁盘读取数据,第二条需要去内存读取数据,程序在执行的时候可能会在去磁盘读取数据的时候先把第二条赋值指令执行完,等从磁盘读完数据再执行第一条赋值指令,这就叫指令重拍序,而volatile保证的有序性即防止指令重排。

2. DCL是否需要volatile关键字修饰?

        先说一下结论:必须要volatile修饰,否则可能会获取到半初始化对象从而引发程序未知错误。下面分析。
        DCL全称Double Check Lock,双重检测锁,是单例模式中非常经典的并发案例,代码如下:

public class Singleton 
	public int num = 6;
	private static /*volatile*/ Singleton singleton;
	private Singleton() 
	public static Singleton getInstance() 
		if (singleton == null) 
			synchornized (Singleton.class) 
				if (singleton == null) 
					singleton = new Singleton();
				
			
		
		return singleton;
	

        在分析singleton是否需要被volatile修饰之前,我们先搞清楚new Singleton()底层指令到底是怎样的,然后再一步步分析
底层汇编指令如下:

 0 new #2 <Singleton>	// new 分配空间, 用默认值清理分配的空间, 即给num赋默认值0
 3 dup // 暂且不说, 与本文无关且比较复杂
 4 invokespecial #3 <Singleton.<init>>	// 调用构造, 调用完了num的值才会为6. 构造过程是给成员变量赋初始值的过程
 7 astore_1	// 建立singleton与对象的关联, 在此之前 singleton == null
 8 return	// 返回、结束

        下面说一个场景,前提是不加volatile。假设现在有2个线程同时调用了getInstance(),假设是第一次调用,a线程开始执行,第一次判空为true,然后a线程获取锁,进入临界区,a线程第二次判空为true,开始创建对象,执行汇编,注意,此时发生了指令重排,创建对象的汇编指令被重排为如下顺序

 0 new #2 <Singleton>
 3 dup
 4 astore_1
 7 invokespecial #3 <Singleton.<init>>
 8 return

        可以看到指令变为先建立引用与对象之间的连接,再初始化对象,一旦建立了连接,引用便不为null,假设a线程执行到创建对象的汇编指令的astore_1指令,b线程开始调用getInstance()方法,此时singleton不为null,b线程第一次判空为false,直接返回半初始化对象,可能就会出现错误,由此可以得出DCL单例模式必须使用volatile修饰,否则可能会出问题。
        能力有限,不足之处欢迎指正~

以上是关于DCL 单例模式是否需要volatile?的主要内容,如果未能解决你的问题,请参考以下文章

单例模式-DCL

DCL并非单例模式专用

Java 设计模式 -- 单例模式的实现(饿汉式枚举饿汉式懒汉式双检锁懒汉式内部类懒汉式)jdk 中用到单例模式的场景DCL实现单例需用volatile 修饰静态变量

Java 设计模式 -- 单例模式的实现(饿汉式枚举饿汉式懒汉式双检锁懒汉式内部类懒汉式)jdk 中用到单例模式的场景DCL实现单例需用volatile 修饰静态变量

单例模式DCL式以及Java指令无序性解析

Java枚举单例模式比DCL和静态单例要好?