双锁单例

原文路径:https://www.cnblogs.com/yanfengfree/p/6271359.html

public class SingleTon {

    private SingleTon{}

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

这种写法相信很多人都见过,但是你认为这种写法是正确的吗?或者更准确的来说,这种写法在并发的环境下是否还能表现出正确的行为呢。

  之所以有这种所谓的双重加锁,一方面是因为延迟初始化可以提高性能,另一方面通过使用内置锁sychronized来防止并发,其原理是首先检查是否在没有同步的情况下进行了初始化,如果没有的话,在进行同步,然后再次检查是否对其(instance)进行了初始化,如果没有那么则初始化DoubleCheckLock。

  这种写法表面看起来既提高了性能,又保证了线程安全。但实际上却并不是如此,我只从线程安全上来分析这种写法的对错。

  在这,首先应该注意的是使用内置锁加锁的是DoubleCheckLock.class,并不是instance,也就是说没有在instance实现同步,那么在这种情况下,当有两个线程同时进行到synchronized代码块时,只有一个线程可以进入,然后初始化了instance,但是这仅仅只能保证的是两个线程在访问上的独占性,也就是说两个线程在此一定是一先一后进行访问,但是不能保证的是instance的内存可见性,原因很简单,因为同步的对象并不是instance,而是DoubleCheckLock.class(可以保证内存可见性)。不能保证内存可见性的后果就是当第一个线程初始化instance之后,第二个线程并不能马上看见instance被初始化,或者更准确的来说,第二个线程看到的可能只是被部分构造的instance。因此,这种造成的后果是第二个线程读取到了错误的instance的状态,有可能instance会被再次实例化。

  那么如何解决这个问题呢,最简单的方式是对instance加上关键词volatile,volatile可以保证变量的内存可见性,同时volatile同步的消耗也非常小,这么做到话,可以保证线程安全。

  上述解决问题的方式固然是可以,但是实质上我感觉很繁琐其代码阅读效果也不好,就单例而言,我推荐一下的写法。

public class SingleTon {

    private SingleTon{}

    private static class SingleTonHolder{
        public static SingleTon instance = new SingleTon();
    }

    public static SingleTon getInstance() {
        return  SingleTonHolder.instance;
    }
}

这种写法相对而言比较简单,而且处理了两个问题:1.线程安全问题。2.延迟初始化(初始化在调用getInstance的时候才会去静态内部类中初始化instance)。而且相对而言,有着更加良好的代码可读性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值