懒汉式单例演进到DCL懒汉式 深度全面解析


懒汉式单例可以分为4个演进过程,接下来将阶段展示,同时解析各自的优缺点以及优化方式和原理。

第一阶段:普通的懒汉式单例

public class Singleton {
    private static Singleton singleton = null;

    /**
     * 第一阶段:普通模式的懒汉式单例,需要注意的有两点:
     *   1:构造方法要用private修饰,目的:将构造方法保护起来,在其他类中不能调用单例模式的构造方法,
     *      以免破坏单例
     *   2:调用获取实例方法getInstance()的时候,先判断对象是否已经初始化。
     *  至此普通懒汉式单例完成。但是存在线程安全问题
     *
     *    if判断存在线程安全问题,在并发场景下,可能会有多个线程都进入if,导致创建多个实例。
     */
    private Singleton(){
    }
    public static Singleton getInstance(){
        //该模式存在问题:if判断存在线程安全问题,在并发场景下,可能会有多个线程都进入if,导致创建多个实例。
        if(singleton ==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

第二阶段:加锁懒汉式单例

public class Singleton {
    private static Singleton singleton = null;
    private static final Object lock = new Object();
    /**
     * 第二阶段:为了解决第一阶段的线程安全问题,采用加锁懒汉式单例
     *   优化方式: 加锁。
     *   加锁的目的:为了解决第一阶段if判断存在的线程安全问题,加锁后,可以解决线程安全问题。
     *   存在的缺点:加锁是比较耗费性能的,加锁后,每个线程调用getInstance()方法时,都要先加锁,其实只
     *             需要在实例化的时候加锁保证线程安全就行了,以后再获取单例的时候不需要重新实例化,会
     *             直接return已实例化的对象,但是目前的锁,在对象实例化完毕之后,也会让每个来获取对
     *             象的线程都先加锁,影响性能。
     *  存在的问题:该模式的懒汉式,虽然保证了线程安全,但是由于锁的粒度过大,影响性能。
     */
    private Singleton(){
    }
    public static Singleton getInstance(){
        synchronized(lock){
            if(singleton == null){
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

第三阶段:普通DCL懒汉式单例

public class Singleton {
    private static Singleton singleton = null;
    private static final Object lock = new Object();

    /**
     * 第三阶段:由于第二阶段锁的粒度过大,影响性能,于是对第二阶段进行优化
     *   优化的方式:在进入synchronized代码块之前再加一层if(singleton == null)的判断;
     *        目的:在外面套一层if判断后,只要对象第一次实例化后,后续进来的线程if判断出对象已经实例化,
     *             就不需要加锁了,直接return实例化好的对象。优化了第二阶段所有调用getInstance()的线
     *             程都加锁造成的损耗性能问题。
     *  存在的问题:由于在synchronized代码块外面加了一层if(singleton == null)的判断,导致将singleton
     *            变量暴露在了synchronized()代码块外面,synchronized可以保证原子性、可见性,但是无法
     *            阻止指令重排序,synchronized()代码块内部的代码在获得cpu的使用权时,有可能被指令重排
     *            序,该指令重排序会带来一个严重的问题:变量singleton在synchronized内部会被先赋值。
     *            从字节码指令分析 new Singleton();会有4个字节码指令
     *              1、创建一个对象
     *              2、复制对象引用
     *              3、调用构造方法
     *              4、将对象引用赋值给变量
     *              由于指令重排序的问题,可能把3和4顺序调换,并发场景下未获取到锁的线程在if(singleton == null)
     *              判断到singleton 不是null。但其实第三步字节码指令构造方法还未执行。
     *            
     *  存在的问题:看似完美的模式,但是如果对有序性理解不深,就会入坑。造成问题的原因:指令重排序。
     *             当然不是说指令重排序不好。只是在该场景下如果有并发,指令重排序会带来其他线程可
     *             能获取到还未实例化对象的问题
     */
    private Singleton(){
    }
    public static Singleton getInstance(){
        if(singleton == null){
            synchronized(lock){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

第四阶段:终极版DCL懒汉式单例

public class Singleton {
    private static volatile Singleton singleton = null;
    private static final Object lock = new Object();

    /**
     * 第四阶段:针对第三阶段双重if,可能会由于指令重排序带来的问题,进行优化
     *   优化的方式:private static Singleton singleton; 加 volatile 修饰;
     *        目的:volatile可以保证可见性、有序性,加了volatile修饰,将会保证有序性,volatile通过
     *             读写屏障保证有序性,singleton被volatile修饰,在singleton赋值的时候添加写屏障,
     *             保证写屏障后面的指令不会出现在singleton赋值的前面。
     *          由于添加了volatile,保证了有序性,所以至此,DCL懒汉式单例完成。DCL懒汉式单例既保证
     *          了线程安全,又优化了加锁带来的性能损耗问题,同时有了volatile的修饰,也保证了不会由于发
     *          生指令重排,造成线程并发时获取到还未实例化的对象问题。
     *
     *   至此DCL懒汉式单例终极版完成。
     */
    private Singleton(){
    }
    public static Singleton getInstance(){
        if(singleton == null){
            synchronized(lock){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

小结

一个单例模式其实用到了Java并发方面的知识,想写好一个懒汉式单例并且理解原理还是不太容易的。代码就是一个不断优化的过程,然后优化的方式在解决原有问题的同时也可能带来新的问题,接着继续优化,循序渐进的让代码趋于完美

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值