Double-Checked Locking with Singleton – 用Singleton进行双重检查的锁定

最后修改: 2018年 4月 23日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll talk about the double-checked locking design pattern. This pattern reduces the number of lock acquisitions by simply checking the locking condition beforehand. As a result of this, there’s usually a performance boost. However, it should be noted that the double-checked locking is a broken declaration.

在本教程中,我们将讨论双重检查锁定的设计模式。这种模式通过简单地事先检查锁定条件来减少锁的获取次数。这样做的结果是,通常会有一个性能提升。然而,需要注意的是,双检查锁是一个破碎的声明/strong>。

Let’s take a deeper look at how it works.

让我们更深入地看一下它是如何工作的。

2. Implementation

2.实施

To begin with, let’s consider a simple singleton with draconian synchronization:

首先,让我们考虑一个简单的单子,有苛刻的同步。

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

    // private constructor and other methods ...
}

Despite this class being thread-safe, we can see that there’s a clear performance drawback: each time we want to get the instance of our singleton, we need to acquire a potentially unnecessary lock.

尽管这个类是线程安全的,但我们可以看到有一个明显的性能缺陷:每次我们想获得我们的单子的实例时,我们需要获得一个潜在的不必要的锁。

To fix that, we could instead start by verifying if we need to create the object in the first place and only in that case we would acquire the lock.

为了解决这个问题,我们可以从验证我们是否需要首先创建对象开始,只有在这种情况下我们才会获得锁。

Going further, we want to perform the same check again as soon as we enter the synchronized block, in order to keep the operation atomic:

进一步说,我们希望在进入同步块时再次进行同样的检查,以保持操作的原子性。

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...
}

One thing to keep in mind with this pattern is that the field needs to be volatile to prevent cache incoherence issues. In fact, the Java memory model allows the publication of partially initialized objects and this may lead in turn to subtle bugs.

这种模式需要注意的一点是,字段需要是volatile的,以防止缓存不一致的问题。事实上,Java内存模型允许公布部分初始化的对象,这可能反过来导致微妙的错误。

3. Alternatives

3.替代品

Even though the double-checked locking can potentially speed things up, it has at least two issues:

尽管双重检查的锁定有可能加快事情的进展,但它至少有两个问题。

  • since it requires the volatile keyword to work properly, it’s not compatible with Java 1.4 and lower versions
  • it’s quite verbose and it makes the code difficult to read

For these reasons, let’s look into some other options without these flaws. All of the following methods delegate the synchronization task to the JVM.

基于这些原因,我们来看看其他一些没有这些缺陷的选择。以下所有的方法都是将同步任务委托给JVM。

3.1. Early Initialization

3.1.早期初始化

The easiest way to achieve thread safety is to inline the object creation or to use an equivalent static block. This takes advantage of the fact that static fields and blocks are initialized one after another (Java Language Specification 12.4.2):

实现线程安全的最简单方法是内联对象的创建,或者使用一个等价的静态块。这就利用了静态字段和块是一个接一个初始化的事实(Java语言规范12.4.2)。

public class EarlyInitSingleton {
    private static final EarlyInitSingleton INSTANCE = new EarlyInitSingleton();
    public static EarlyInitSingleton getInstance() {
        return INSTANCE;
    }
    
     // private constructor and other methods...
}

3.2. Initialization on Demand

3.2.按需初始化

Additionally, since we know from the Java Language Specification reference in the previous paragraph that a class initialization occurs the first time we use one of its methods or fields, we can use a nested static class to implement lazy initialization:

此外,由于我们从上一段的《Java语言规范》参考资料中知道,类的初始化发生在我们第一次使用它的一个方法或字段时,所以我们可以使用一个嵌套的静态类来实现懒惰初始化。

public class InitOnDemandSingleton {
    private static class InstanceHolder {
        private static final InitOnDemandSingleton INSTANCE = new InitOnDemandSingleton();
    }
    public static InitOnDemandSingleton getInstance() {
        return InstanceHolder.INSTANCE;
    }

     // private constructor and other methods...
}

In this case, the InstanceHolder class will assign the field the first time we access it by invoking getInstance.

在这种情况下,InstanceHolder类将在我们第一次通过调用getInstance.来访问它时分配字段。

3.3. Enum Singleton

3.3.Enum Singleton

The last solution comes from the Effective Java book (Item 3) by Joshua Block and uses an enum instead of a class. At the time of writing, this is considered to be the most concise and safe way to write a singleton:

最后一个解决方案来自Joshua Block的Effective Java一书(第3项),使用enum而不是class。在撰写本文时,这被认为是编写单子的最简洁和安全的方法。

public enum EnumSingleton {
    INSTANCE;

    // other methods...
}

4. Conclusion

4.结论

To sum up, this quick article went through the double-checked locking pattern, its limits and some alternatives.

总而言之,这篇快速的文章经历了双重检查的锁定模式,其限制和一些替代方案。

In practice, the excessive verbosity and lack of backward compatibility make this pattern error-prone and thus we should avoid it. Instead, we should use an alternative that lets the JVM do the synchronizing.

在实践中,过多的言语和缺乏向后的兼容性使得这种模式容易出错,因此我们应该避免它。相反,我们应该使用另一种方式,让JVM来做同步化工作。

As always, the code of all examples is available on GitHub.

一如既往,所有例子的代码都可以在GitHub上找到