Singletons in Java – Java中的单项式

最后修改: 2017年 9月 1日

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

1. Introduction

1.介绍

In this quick article, we’ll discuss the two most popular ways of implementing Singletons in plain Java.

在这篇短文中,我们将讨论在普通Java中实现Singletons的两种最流行的方法。

2. Class-Based Singleton

2.基于类的单子

The most popular approach is to implement a Singleton by creating a regular class and making sure it has:

最流行的方法是通过创建一个普通的类来实现Singleton,并确保它具有。

  • A private constructor
  • A static field containing its only instance
  • A static factory method for obtaining the instance

We’ll also add an info property, for later usage only. So, our implementation will look like this:

我们还将添加一个信息属性,只供以后使用。因此,我们的实现将看起来像这样。

public final class ClassSingleton {

    private static ClassSingleton INSTANCE;
    private String info = "Initial info class";
    
    private ClassSingleton() {        
    }
    
    public static ClassSingleton getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new ClassSingleton();
        }
        
        return INSTANCE;
    }

    // getters and setters
}

While this is a common approach, it’s important to note that it can be problematic in multithreading scenarios, which is the main reason for using Singletons.

虽然这是一种常见的方法,但需要注意的是,在多线程情况下可能会出现问题,这也是使用Singletons的主要原因。

Simply put, it can result in more than one instance, breaking the pattern’s core principle. Although there are locking solutions to this problem, our next approach solves these problems at a root level.

简单地说,它可能导致一个以上的实例,破坏了模式的核心原则。虽然这个问题有锁定的解决方案,但我们接下来的方法从根本上解决了这些问题。

3. Enum Singleton

3.Enum Singleton

Moving forward, let’s discuss another interesting approach – which is to use enumerations:

继续往前走,让我们讨论另一种有趣的方法–那就是使用枚举。

public enum EnumSingleton {
    
    INSTANCE("Initial class info"); 
 
    private String info;
 
    private EnumSingleton(String info) {
        this.info = info;
    }
 
    public EnumSingleton getInstance() {
        return INSTANCE;
    }
    
    // getters and setters
}

This approach has serialization and thread-safety guaranteed by the enum implementation itself, which ensures internally that only the single instance is available, correcting the problems pointed out in the class-based implementation.

这种方法的序列化和线程安全由枚举实现本身来保证,它在内部确保只有单一实例可用,纠正了基于类的实现中指出的问题。

4. Usage

4.使用方法

To use our ClassSingleton, we simply need to get the instance statically:

为了使用我们的ClassSingleton,我们只需要静态地获取实例。

ClassSingleton classSingleton1 = ClassSingleton.getInstance();

System.out.println(classSingleton1.getInfo()); //Initial class info

ClassSingleton classSingleton2 = ClassSingleton.getInstance();
classSingleton2.setInfo("New class info");

System.out.println(classSingleton1.getInfo()); //New class info
System.out.println(classSingleton2.getInfo()); //New class info

As for the EnumSingleton, we can use it like any other Java Enum:

至于EnumSingleton,我们可以像其他Java Enum一样使用它。

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();

System.out.println(enumSingleton1.getInfo()); //Initial enum info

EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New enum info");

System.out.println(enumSingleton1.getInfo()); // New enum info
System.out.println(enumSingleton2.getInfo()); // New enum info

5. Common Pitfalls

5 常见的陷阱

Singleton is a deceptively simple design pattern, and there are few common mistakes that a programmer might commit when creating a singleton.

单子是一种具有欺骗性的简单设计模式,程序员在创建单子时可能会犯一些常见错误。

We distinguish two types of issues with singletons:

我们将单体的问题分为两类。

  • existential (do we need a singleton?)
  • implementational (do we implement it properly?)

5.1. Existential Issues

5.1.存在性问题

Conceptually, a singleton is a kind of global variable. In general, we know that global variables should be avoided — especially if their states are mutable.

从概念上讲,单子是一种全局变量。一般来说,我们知道应该避免使用全局变量–特别是当它们的状态是可变的。

We’re not saying that we should never use singletons. However, we are saying that there might be more efficient ways to organize our code.

我们并不是说我们不应该使用单子。然而,我们是说,可能有更有效的方法来组织我们的代码。

If a method’s implementation depends on a singleton object, why not pass it as a parameter? In this case, we explicitly show what the method depends on. As a consequence, we may easily mock these dependencies (if necessary) when performing testing.

如果一个方法的实现依赖于一个单子对象,为什么不把它作为一个参数来传递?在这种情况下,我们明确地显示了方法所依赖的东西。因此,在进行测试时,我们可以轻松地模拟这些依赖关系(如果需要的话)。

For example, singletons are often used to encompass the application’s configuration data (i.e., connection to the repository). If they are used as global objects, it becomes difficult to choose the configuration for the test environment.

例如,单子经常被用来包含应用程序的配置数据(即与存储库的连接)。如果它们被用作全局对象,为测试环境选择配置就会变得很困难。

Therefore, when we run the tests, the production database gets spoiled with the test data, which is hardly acceptable.

因此,当我们运行测试时,生产数据库会被测试数据所破坏,这是很难接受的。

If we need a singleton, we might consider the possibility of delegating its instantiation to another class — a sort of factory — that should take care of assuring that there is just one instance of the singleton in play.

如果我们需要一个单子,我们可以考虑将其实例化委托给另一个类–一种工厂–它应该负责确保只有一个单子的实例在发挥作用。

5.2. Implementational Issues

5.2.实施问题

Even though the singletons seem quite simple, their implementations may suffer from various issues. All result in the fact that we might end up having more than just one instance of the class.

尽管单子看起来很简单,但它们的实现可能存在各种问题。所有这些都会导致我们最终可能拥有不止一个类的实例。

Synchronization
The implementation with a private constructor that we presented above is not thread-safe: it works well in a single-threaded environment, but in a multi-threaded one, we should use the synchronization technique to guarantee the atomicity of the operation:

同步化
我们上面介绍的带有私有构造函数的实现不是线程安全的:它在单线程环境下运行良好,但在多线程环境下,我们应该使用同步技术来保证操作的原子性。

public synchronized static ClassSingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new ClassSingleton();
    }
    return INSTANCE;
}

Note the keyword synchronized in the method declaration. The method’s body has several operations (comparison, instantiation, and return).

注意方法声明中的关键词synchronized。该方法的主体有几个操作(比较、实例化和返回)。

In absence of synchronization, there is a possibility that two threads interleave their executions in such a way that the expression INSTANCE == null evaluates to true for both threads and, as a result, two instances of ClassSingleton get created.

在没有同步的情况下,两个线程有可能以这样的方式交错执行,即表达式INSTANCE == null对两个线程来说都评估为true,结果,两个ClassSingleton的实例被创建。

Synchronization might significantly affect the performance. If this code gets invoked often, we should speed it up using various techniques like lazy initialization or double-checked locking (be aware that this might not work as expected due to compiler optimizations).  We can see more details in our tutorial “Double-Checked Locking with Singleton“.

同步化可能会大大影响性能。如果这段代码经常被调用,我们应该使用各种技术来加速它,比如lazy initializationdouble-checked locking(请注意,由于编译器的优化,这可能无法达到预期效果)。 我们可以在我们的教程”Double-checked Locking with Singleton“中看到更多细节。

Multiple Instances
There are several other issues with the singletons related to JVM itself that could cause us to end up with multiple instances of a singleton. These issues are quite subtle, and we’ll give a brief description for each of them:

多实例
还有几个与JVM本身有关的单子的问题,可能导致我们最终出现一个单子的多个实例。这些问题都很微妙,我们将对每一个问题进行简要的描述。

  1. A singleton is supposed to be unique per JVM. This might be a problem for distributed systems or systems whose internals are based on distributed technologies.
  2. Every class loader might load its version of the singleton.
  3. A singleton might be garbage-collected once no one holds a reference to it. This issue does not lead to the presence of multiple singleton instances at a time, but when recreated, the instance might differ from its previous version.

6. Conclusion

6.结论

In this quick tutorial, we focused on how to implement the Singleton pattern using only core Java, and how to make sure it’s consistent and how to make use of these implementations.

在这个快速教程中,我们着重介绍了如何只用核心Java来实现Singleton模式,以及如何确保它的一致性和如何利用这些实现。

The full implementation of these examples can be found over on GitHub.

这些例子的完整实现可以在GitHub上找到over