双重检测锁单例模式指令重排问题

前言

相信大多数同学在面试当中都遇到过手写单例模式的题目,那么如何写一个完美的单例是面试者需要深究的问题,因为一个严谨的单例模式说不定就直接决定了面试结果,今天我们就要来讲讲看似线程安全的双重检测锁单例模式中可能会出现的指令重排问题。

双重检测锁单例模式例子

乍一看下面单例模式没啥问题,还加了同步锁保证线程安全,从表面上看确实看不出啥问题,当在同一时间多个线程同事执行该单例时就会出现JVM指令重排的问题,从而可能导致某一个线程获取的single对象未初始化对象。

public class Single {

	private static Single single;
	
	private Single() {
	}
	
	public static Single getInstance() {
		if(null == single) {
			synchronized (Single.class) {
				if(null == single) {
					single = new Single();
				}
			}
		}
		return single;
	}
}

问题前因后果

其实single = new Single()这段代码并不具备原子性,从代码层面上来说确实没问题,但是如果了解JVM指令的就知道其实在执行这句代码的时候在JVM中是需要执行三个指令来完成的,如下:

memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址

看到上面指令重排的解释之后,那么我们来回顾一下未加volatile修饰符的单例为何会出现问题。
假设有A、B两个线程去调用该单例方法,当A线程执行到single = new Single()时,如果编译器和处理器对指令重新排序,指令重排后:

memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
ctorInstance(memory); //2:初始化对象

当A线程执行到第二步(3:设置instance指向刚分配的内存地址,此时对象还没被初始化)变量single指向内存地址之后就不为null了,此时B线程进入第一个if,由于single已经不为null了,那么就不会执行到同步代码块,而是直接返回未初始化对象的变量single,从而导致后续代码报错。

解决方案

问题也搞清楚了,接下来我们就来看下如何解决这个问题。
解决问题的关键就在于volatile关键字,我们来看下volatile关键字的特性。
可见性:

  • 写volatile修饰的变量时,JMM会把本地内存中值刷新到主内存 读
  • volatile修饰的变量时,JMM会设置本地内存无效

有序性:
要避免指令重排序,synchronized、lock作用的代码块自然是有序执行的,volatile关键字有效的禁止了指令重排序,实现了程序执行的有序性;
看完volatile关键字的特性之后我们应该就明白了,是volatile关键字禁止了指令重排序从而解决了指令重排的问题。

更改后的单例

对比上面单例,下面单例在私有静态变量single前面加了修饰符volatile,能够防止JVM指令重排,从而解决了single对象可能出现成员变量未初始化的问题。

public class Single {

	private volatile static Single single;
	
	private Single() {
	}
	
	public static Single getInstance() {
		if(null == single) {
			synchronized (Single.class) {
				if(null == single) {
					single = new Single();
				}
			}
		}
		return single;
	}
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值