double判断空_double-checked locking实现单例模式的一些探讨

本文深入探讨了双重检查锁定(DCL)单例模式在高并发环境下的问题,即可能出现空指针异常的原因,这源于指令重排序。解决方案是使用`volatile`关键字防止指令重排序,或者采用类加载机制的内部类实现单例,以确保线程安全。同时,文章讨论了`volatile`带来的性能影响以及如何平衡安全与效率。
摘要由CSDN通过智能技术生成

问题

下面是一段双重检查锁实现单例模式的代码

public 

咋一看是没什么问题的,先私有化构造方法,不让外界调用,然后通过double-checked locking来初始化INSTANCE。

大部分情况下,这么写的确不会有问题,但是少部分线程竞争特别激烈的情况,这么写会返回空

为什么

为什么会报空指针,这是因为初始化INSTANCE = new Singleton()这行代码,不是原子操作,而这内部可能存在指令重排序

java中new一个对象并赋值给静态变量,实际上存在着以下几步操作:

  1. 在堆内存中创建新的对象
  2. 将一份对象的引用用于调用类的构造方法进行初始化
  3. 将一份对象的引用与静态变量相关联

上面2,3步是完全独立的,不管哪条语句先执行都对结果没影响,因此如果构造方法逻辑复杂,相对耗时的时候,jvm会进行一个指令重排序,使3语句在2语句的前面执行,而恰巧这时候有多个线程访问,这个时候情况是这样的:

  1. 线程1执行外层if,此时instance为空,进入判断
  2. 线程1获取锁
  3. 在堆内存中创建新的对象
  4. 线程1将堆内存的对象与instance进行关联,关联后instance不再是null,但此时由于上面中的2语句未执行,instance未被初始化
  5. 线程2进入外层if
  6. 由于第四步,instance不再是null,但又由于instance未被初始化,所以最终返回的是一个未被初始化的instance对象,线程2return null;
  7. 线程1继续执行
  8. ....

继续探讨

为什么说INSTANCE = new Singleton()不是原子操作,我们可以反编译下class,看生成的字节码:

17: 

17行new,新建一个Singleton对象,并拿到它的引用压入操作数栈中

20行dup,复制对象的引用压入操作数栈中

21行invokespecial,从操作数栈用弹出一个引用,用于调用类的构造方法进行初始化

24行putstatic,从操作数栈用弹出一个引用用于给成员变量赋值

其中21,24行存在指令重拍序的可能

如何解决

既然是指令重排序导致这个问题,那么就只要防止指令重排序就行了,我们知道java中的volatile关键字可以防止指令重排的问题,那么只需要对Instance使用volatile关键字修饰即可

private 

更优解

volatile虽然可以防止指令重排序的问题,但这也导致了每次线程都是从主内存中获取INSTANCE的值,也会存在一定的性能上的浪费,如何解决这种问题呢?可以用单例的另一种实现方式,利用类加载的线程安全性,使用内部类来初始化instance,见代码:

public 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值