单例,二次判空校验,指令重排

网上文章已很完备,此处引用下

大聪明教你学Java | 单例模式为什么要进行二次判空_不肯过江东丶的博客-CSDN博客_单例两次判空

java单例模式双重检验锁的volatile和两次判空 - 简书

设计模式-单例模式-指令重排思考_AlaGeek的博客-CSDN博客_单例模式指令重排

简单解释下:

public class Singleton {

    private volatile static Singleton instance = null;
    private Singleton(){}

    public static Singleton getInstance(){
        //第一次判空
        if (instance == null) {
            synchronized(Singleton.class){
                //第二次判空
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

第一次判空
拿掉第一次判空后,执行的时候会直接运行synchronized,所以在synchronized锁前面再加一次判断,就大大降低synchronized块的执行次数——降低了获取锁和释放锁的开销。

第二次判空
【并发】假如现在有两个线程,分别是线程A 和 线程B,首先两个线程执行到第一个if ,这时线程A先拿到了锁,线程B就被堵塞了;接下来线程A完成了instance的初始化,创建了内存,然后线程A就释放了锁;回头再看看一直被堵塞的线程B,线程A释放了锁以后,线程B就拿到了锁,继续向下执行,如果此时没有第二次判空 ,那么就还会再执行new操作,这样就可以达到——防止重复创建的目的(也破坏了单例),节省了内存资源。

正确的指令执行顺序:
①、如果类没有被加载过,则进行类的加载
②、在堆中开辟内存空间 adr,用于存放创建的对象
③、执行构造方法实例化对象
④、将堆中开辟的内存地址 adr 赋值给被volatile修饰的Instance引用变量

【并发】如果引用变量instance不使用volatile修饰的话,则可能由于编译器和处理器对指令进行了重排序,导致第④步在第③步之前执行,此时,instance引用变量不为null了(已开辟内存空间,引用地址adr有了),但是Instance这个引用变量所指向的堆中内存地址中的对象是还没被实例化的(地址里面没有对象--尚未实例化),实例对象还是null的——那么在第一次判空时就不为null了,然后去使用时就会报NPE空指针异常了(引用变量instance以为!=null,却,实质的对象尚为null。不并发的时候似乎重排就重排了,指向的地址空间稍后会被塞入实例化对象,对于单线程而言它最终结果是一样的,但多线程其他线程拿到个null的去马上用就会NPE了)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值