17 Mar 2023

Java singleton

Not long time ago, I discovered in Java it is possible to implement a Singleton with an Enum. And not only that, it is the recommended way to implement them!

In this post, we explore the best practices when implementing the singleton pattern in Java.

1. Introduction

The following text is extracted from the wikipedia 1:

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to a singular instance. One of the well-known "Gang of Four" design patterns 2, which describes how to solve recurring problems in object-oriented software, the pattern is useful when exactly one object is needed to coordinate actions across a system.

More specifically, the singleton pattern allows objects to:

  • Ensure they only have one instance
  • Provide easy access to that instance
  • Control their instantiation (for example, hiding the constructors of a class)

The term comes from the mathematical concept of a singleton 3.

In this post, we'll dive into the best practices for implementing the singleton pattern in Java, considering key features such as concurrency, reflection, and serialization.

2. Java features to consider when creating a singleton

2.1. Concurrency

When creating a singleton in Java we need to make sure it is thread safe4. That is, when two or more threads instantiate a singleton only a single instance is created.

For this purpose, we will leverage the static keywork. The keyword static means that the particular member belongs to a type itself, rather than to an instance of that type.

From the java docs5: "If a field is declared static, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created.

2.2. Reflection

In the later examples, we will make use of a private contructor to implement the Singleton. With the help of Reflection6, an attacker could modify the runtime behaviour of the application modifying the accessibility of the private constructor to public. In other words, with the help of Reflection it would be possible to create a second instance of the class.

2.3. Serialization

Serialization is the process of translating an object into a format that can be stored or transmitted and reconstructed later. See figure 1.

Serialization.jpg
Figure 1: Serialization [WnbKrumov, CC BY-SA 3.0, via Wikimedia Commons].

In Java, serializability of a class is enabled by the class implementing the java.io.Serializable interface.

Now, imagine your application serializes your singleton instance, so far so good, a stream of bytes has been created. However, when your application deserialize the same stream of bytes, it might end creating a new instance, leading, in the case of our example, to have two different instances!

Therefore we need to be carefull with Serialization when implementing our Singleton.

3. Singleton implementation

The two common ways of implementing singletons are based on keeping the constructor private and exporting a public static member to provide access to the instance.

In this first approach, the member INSTANCE is a final static field. The private constructor is called only once, to initialize the public static final field Spyderman.INSTANCE.

// Singleton with public final field
public class Spyderman {
    public static final Spyderman INSTANCE = new Spyderman();
    private Spyderman() { ... }
    public void withGreatPowerComesGreatResponsibility() { ... }
}

Thanks to the static final INSTANCE, we make sure the singleton is thread safe 2.1. However, an attacker could change thanks to reflection 2.2 the accessibility to the constructor, making it public or private and invoking it twice or more. We can defend ourselves against this attack making the constructor to throw an exception if it is invoked to create a second instance.

In the second approach to implement sigletons, the public member is a static method:

// Singleton with static factory
public class Spyderman {
    private static final Spyderman INSTANCE = new Spyderman();
    private Spyderman() { ... }
    public static Spyderman getInstance() { return INSTANCE; }
    public void withGreatPowerComesGreatResponsibility() { ... }
}

All calls to Spyderman.getInstance() return the same object reference. Same as the first example, it is thread safe thanks to the statics fields and it has the same caveat with reflection.

This second implementation is the prefered one, as it is more flexible, allowing the developer to remove the singleton property without changing the API as well as implement one singleton per thread or incorparating generics.

Now, if we need to make one of the above approaches Serializable adding implements Serializable, we need to modify our code to keep the singleton guarantee. Otherwise, each time a serialized instance is deserialized, as explained before 2.3, a new instance will be created. The solution would be to declare all instance fields transient and implement the readResolve method. We can find examples of this in the java.util.Collections7 file.

There is a third approach to the Singleton pattern, where we do not have to deal with thread safety, reflection nor serialization. It is the enum approach:

// Enum singleton - the preferred approach
public enum Spyderman {
    INSTANCE;
    public void withGreatPowerComesGreatResponsibility() { ... }
}

This approach look at first a bit inuntuituve. But lets check it properties. Every enum constant is always implicitly public static final which make it thread safe. There is no constructor that can be called by the developer so it makes it safe againts reflection attacks and as per documentation8 Enums are Serializable and special methods like readResolve are ignored.

The main disadvante is that Enums can not extend a superclass other than Enum, luckily, we can implement other interfaces.

4. Summary

We have seen why the Enum approach is often the best way to implement a singleton. The jvm provides the serialization and the protection against multiple instantiation and reflection attacks for free.

Footnotes:

Tags: java design-patterns
Other posts
Creative Commons License
paconte.com by Francisco Javier Revilla Linares is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.