双重检查锁的问题与解决

在我们写单例的时候,我们可能会有如下写法:

public class Test{
    private static Instance instance;
    
    public synchronized static Instance getInstance(){
        if(instance == null){                //1
            instance= new Instance();        //2
        }
        return instance;
    }
}

上面是一个线程安全的单例模式,没有问题,但是我们每次都是把整个方法锁死,那么对于频繁调用,性能不是很好。

于是,我们来优化一下。

public class Test{
    private static Instance instance;
    
    public  static Instance getInstance(){
        if(instance == null){       
            synchronized(Test.class){
                if(instance == null){            //1
                     instance= new Instance();   //2
                }
            }
        }
        return instance;
    }
}

这是一种双重检验的写法,看起来非常“聪明”,如果第一次检查不为null,那么就不需要执行下面操作,可以给性能带来提升。

但是我们深入的去看看这个写法:

在创建对象的过程中,首先1.加载class对象,2.然后分配内存,3.初始化对象,4.最后添加引用。

但是在一些JIT的编译器上,3,4两个步骤可能会被重排序的(可以被从排序的前提是不影响程序执行结果,在这里并没有影响执行结果)。

那么如果线程A在初始化对象的过程被重排许了,先把引用给了A,那么线程B去拿共享变量,当B线程去读取共享变量的数据时发现为空,产生错误。那么这种情况也是不允许存在的。

那么我们要怎么解决这个问题呢?

那我们既然知道这个问题是由指令重排序引起的,那么我们就以重排序入手。

1.让程序不进行重排序。

2.使重排序对其他线程不可见。

第一种解决方案就是禁止指令重排序,即使用我们的volatile关键字。

public class Test{
    private volatile static Instance instance;
    
    public  static Instance getInstance(){
        if(instance == null){       
            synchronized(Test.class){
                if(instance == null){            //1
                     instance= new Instance();   //2
                }
            }
        }
        return instance;
    }
}

第二种解决方案就是使重排序对其他县城不可见,使用静态内部内的方式。

public class Test{
    
    private static class InstanceHolder{
        public static Instance instance  = new Instance();
    }
    
    public static Instance getInstance(){
        return InstanceHolder.instance;
    }
}

个人比较推荐第二种方式。当然单例模式还有其他写法。就不一一举例了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值