Java高频面试题:在DCL单例写法中,为什么主要做两次检查?

简介: 有位工作5年的小伙伴,面试的时候被问到这样一道题,说在DCL单例写法中,为什么要做两次检查。要回答好这个问题,需要知道DCL单例的写法以及为什么要这样写?今天,我给大家详细分析一下。

有位工作5年的小伙伴,面试的时候被问到这样一道题,说在DCL单例写法中,为什么要做两次检查。要回答好这个问题,需要知道DCL单例的写法以及为什么要这样写?


今天,我给大家详细分析一下。

1、什么是DCL

DCL是一种单例模式写法的简称,全称是Double Check Lock,翻译过来叫双重检查锁。从命名上来理解,就是两次检查加一把锁。那么,两次检查又是检查什么,锁又是锁的什么?


首先,来看这样一段代码,这是比较标准的DCL单例写法。

86b726369e0a66da11a94f383a8bd02f.jpg

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

从代码中,我们发现两次检查的判断条件都是 null == instance,而且两个检查条件是嵌套的。在第1次检查条件的代码块中,加了一段synchronized代码块,synchronized就是锁。


小伙伴们应该都知道,加锁是为了保证线程安全,检查是为了保证内存中只有一个实例。既然,这两个条件是嵌套的,那是不是可以去掉一个条件呢?下面,我们来详细分析一下。

2、为什么需要两次检查

我们重点来看这段代码,

e9609a1332aef30998223080e3768350.jpg

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

假设,我们去掉第1次检查,只保留第2次检查,如代码所示:


现在,有两个线程T1、T2同时访问getInstance()方法,当T1进入到synchronized代码块的时候,T2就会阻塞。直到T1执行完成释放CPU资源,T2才能获得锁。

2195bf541fe4dc654b98a3a920113290.jpg

T1执行检查条件时,null == instance为ture满足条件,因此,会创建1个新的对象,而T2执行检查条件时,不满足条件,就会直接返回T1创建好的对象,这样就保证了单例。

3463cc1641219b47f61dac14ca3c8e23.jpg

可是,问题来了,后续如果再有其他线程T4、T5、T6出现并发,同时调用getInstance()方法的情况依然会阻塞,

05e498d452784df3801063bc3a95c54c.jpg

相当于,不管单例对象是否已经创建,每次调用都可能阻塞,会影响程序的执行效率。所以,加上第1次检查的目的是,保证只有第一次出现并发的情况会阻塞,提高性能。


那假设,去掉第2次检查,只保留第1次检查,如代码所示:

d683c40ec1ee3423b990786eb175ec06.jpg

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

还是,线程T1、T2同时访问getInstance()方法,此时T1、T2同时满足条件,两个线程会按顺序执行synchronized代码块中的逻辑。假设T1先执行创建对象,那么,T2获得锁的时候,依然会创建对象,而且还会覆盖T1创建的对象,

136c1d75f7844f15cf989d6d2abec14e.jpg

这就相当于破坏了单例。


因此,第2次检查的目的是,保证单例,避免重复创建单例对象。

3、总结

通过前面的分析,我们得出结论,DCL单例写法中,

54ef9a86075133fc0fe9fa662028e004.jpg

第1次检查是为了保证只有首次并发的情况下才阻塞,提高性能,第2次检查是为了保证,避免重复创建对象。加锁,当然就是为了保证线程安全。


在今天的分享,我还有一个细节没有讲到,就是在并发情况下,new一个对象可能会出现指令重排的现象。这时候,我们需要给声明的单例对象加上volatile关键字,保证可见性。

97393cd6e1c02d6f14181216bee5a751.jpg

public class LazyDoubleCheckLockSingleton {
    private static volatile LazyDoubleCheckLockSingleton instance = null;
}

至于volatile关键字为什么能解决指令重排的问题,小伙伴们可以去我的主页,在往期视频中有专门详细分析这个问题,本期视频就不重复讲解了。


好了,以上就是我对DCL两次检查的理解。


我是被编程耽误的文艺Tom,关注我,面试不再难!

9106b97c16b34d06af118b23d081cde9.gif

相关文章
|
15小时前
|
JavaScript 前端开发 Java
【JAVA面试题】什么是引用传递?什么是值传递?
【JAVA面试题】什么是引用传递?什么是值传递?
|
2天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
2天前
|
Java
[Java 面试题] ArrayList篇
[Java 面试题] ArrayList篇
|
3天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
29 1
|
3天前
|
存储 安全 Java
每日一道Java面试题:说一说Java中的泛型?
今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。
15 0
|
3天前
|
Java 编译器
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
15 0
|
7天前
|
XML 缓存 Java
Java大厂面试题
Java大厂面试题
20 0
|
7天前
|
存储 安全 Java
Java大厂面试题
Java大厂面试题
15 0
|
7天前
|
存储 安全 Java
Java大厂面试题
Java大厂面试题
16 0
|
8天前
|
安全 Java
就只说 3 个 Java 面试题 —— 02
就只说 3 个 Java 面试题 —— 02
19 0