为什么在Java中设计单例模式时需要双重检查锁定?

时间:2019-02-20 06:40:57

标签: java design-patterns synchronization singleton

为什么我们需要在获取锁之前和之后检查null? 一次,我们获得了锁,没有线程可以拥有该锁,那么为什么在同步块之前不需要进行null检查呢?

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

    // private constructor and other methods...
}

3 个答案:

答案 0 :(得分:5)

想象下一个场景:

  1. 线程1检查instance == null并发现此条件为真。
  2. 线程2检查instance == null并发现此条件为真。
  3. 线程1获取锁。
  4. 线程2尝试获取锁,它已经被获取,因此线程2等待。
  5. 线程1初始化instance = new DclSingleton()
  6. 线程1释放锁。
  7. 线程2获取锁。
  8. 线程2初始化instance = new DclSingleton()我们有两次初始化

答案 1 :(得分:2)

您两次检查null是因为:

  1. 如果您未在DclSingleton.class上进行同步之前进行检查,则每个呼叫都将被同步,这可能会很慢(可以想象经常使用单例实例)。
  2. 如果您不检查null内的synchronized 块,则有可能多个线程进行了第一次检查而又没有机会锁定对象,然后您将重新创建实例。

答案 2 :(得分:0)

要了解为什么需要进行两次null检查,请查看已经给出的答案。

安全初始化单例实例的另一种方法是静态持有人模式,其实现方式如下:

public class DclSingleton {

    public static DclSingleton getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final DclSingleton INSTANCE = new DclSingleton();
     }
}

JVM已以线程安全的方式初始化类,因此,即使2个线程同时访问getInstance(),JVM也会仅初始化Holder类一次,因此您可以进行正确的初始化。

此外,Holder类将延迟加载,因此仅在首次引用时才将其初始化。例如,getInstance()是第一次被调用