Android Performance

A Detailed Guide to Java Singleton Pattern

Word count: 1.1kReading time: 6 min
2015/05/06
loading

The Singleton pattern, also known as the single-instance pattern, is a widely used software design pattern. When apply this pattern, the class must ensure that only one instance of the singleton object exists. In this article, we will explore the two primary ways to construct a singleton pattern and finally introduce a sophisticated yet concise approach.

What is the Singleton Pattern?

The Singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance.

The core principle is: the singleton class must guarantee that only one instance of the object exists.

Constructing the Singleton Pattern

In Java, there are two main construction approaches:

  1. Lazy Initialization: The singleton instance is created only when it is first used.
  2. Eager Initialization: The singleton instance is created when the class is loaded.

In simpler terms, one delays initialization, while the other does not.

A straightforward implementation is:

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private final static Singleton INSTANCE = new Singleton();

// Private constructor suppresses instantiation
private Singleton() {
}

// Public static method to get the instance
public static Singleton getInstance() {
return INSTANCE;
}
}

This approach is simple to implement, and the instance is created when the class is loaded. But what if we want to implement lazy initialization, where the instance is created only upon its first use?

One common technique is Double-Checked Locking (DCL):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
private static volatile Singleton INSTANCE = null;

// Private constructor suppresses instantiation
private Singleton() {
}

// Thread-safe and performance-oriented
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
// When multiple threads pass the first null check simultaneously,
// we check again to avoid multiple instantiations.
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

Note that this method is only reliable on JDK 5 and later because it requires the volatile keyword to prevent instruction reordering. In versions prior to JDK 5, double-checked locking could lead to unexpected behavior.

Another Singleton Pattern

A highly recommended alternative is the Lazy Initialization Holder Class Idiom:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {

// Private constructor suppresses instantiation
private Singleton() {
}

private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}

The JVM performs class initialization after loading the class but before any thread uses it. During this stage, the JVM acquires a lock to synchronize multiple threads’ initialization of the same class. Compared to other solutions like double-checked locking, this approach is more concise and works across all compiler versions.

For a discussion on why the access level of the static final Singleton INSTANCE field is package-private, you can read: Discussion on the implementation of the Initialization-On-Demand Holder idiom.

Using Enum

Finally, the most concise way to implement a singleton is by using an Enum.

The code is extremely simple, and its usage is straightforward:

1
2
3
public enum Singleton {
INSTANCE;
}

Reflecting on the previous implementations, they all only considered standard object retrieval. However, objects can also be retrieved through serialization and reflection. For the other methods, if they implement the Serializable interface, they must override the readResolve() method:

1
2
3
private Object readResolve() {
return INSTANCE;
}

Even with readResolve(), some fields might still require the transient keyword. Serialization is generally a hassle.

Regarding reflection, typical private constructors only restrict access during compilation, not at runtime. However, an enum-based singleton provides built-in protection against both serialization and reflection. To understand why, look at the decompiled code of an enum class (for reference):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public abstract class Singleton extends Enum
{
private Singleton(String s, int i)
{
super(s, i);
}

public static Singleton[] values()
{
Singleton[] asingleton = ENUM$VALUES;
int i = asingleton.length;
Singleton[] asingleton1 = new Singleton[i];
System.arraycopy(asingleton, 0, asingleton1, 0, i);
return asingleton1;
}

public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(Singleton.class, s);
}

public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[];

static
{
INSTANCE = new Singleton("INSTANCE", 0);
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
}

Enums inherit from java.lang.Enum. You can see that the singleton instance is implemented using a static initialization block.

Why can enums prevent reflection? Simply because it is an abstract class (public abstract class Singleton extends Enum), which cannot be instantiated even via reflection.

Why can they prevent serialization issues? This depends on how Java handles object serialization.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// java.io.ObjectOutputStream

private void writeObject0(Object obj, boolean unshared) {
// ...
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
}
// ...
}

/**
* Writes given enum constant to stream.
*/
private void writeEnum(Enum en, ObjectStreamClass desc, boolean unshared) throws IOException {
bout.writeByte(TC_ENUM);
ObjectStreamClass sdesc = desc.getSuperDesc();
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
handles.assign(unshared ? null : en);
writeString(en.name(), false);
}

When an enum is serialized, only its name is written to the stream. The deserialization process then uses that name to retrieve the existing instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// java.io.ObjectInputStream
private Object readObject0(boolean unshared) throws IOException {
// ...
switch (tc) {
// ...
case TC_ENUM:
return checkResolve(readEnum(unshared));
// ...
}
}

private Enum<?> readEnum(boolean unshared) throws IOException {
// ...
try {
en = Enum.valueOf(cl, name);
} catch (IllegalArgumentException ex) {
// ...
}
// ...
return en;
}

Since deserialization merely calls Enum.valueOf(Class<T> enumType, String name), it retrieves the existing instance rather than creating a new one.

Tip: Be careful when serializing enums; the name of the enum constant must not change, otherwise deserialization will fail with an exception!

  1. Effective Java, Item 71: Use lazy initialization judiciously.
  2. Core Java Volume I, Section 14.5.8: Volatile Fields.
  3. JLS 17.4: Memory Model
  4. Java Theory and Practice: Using Volatile Variables Correctly
  5. Double-Checked Locking and Lazy Initialization

About Me && Blog

Below are my personal details and links. I look forward to connecting and sharing knowledge with fellow developers!

  1. About Me: Includes my WeChat and WeChat group links.
  2. Blog Navigation: A guide to the content on this blog.
  3. Curated Android Performance Articles: A collection of must-read performance optimization articles. Self-nominations/recommendations are welcome!
  4. Android Performance Knowledge Planet: Join our community for more insights.

“If you want to go fast, go alone. If you want to go far, go together.”

WeChat QR Code

CATALOG
  1. 1. What is the Singleton Pattern?
  2. 2. Constructing the Singleton Pattern
  3. 3. Another Singleton Pattern
  4. 4. Using Enum
  5. 5. Recommended Reading:
  • About Me && Blog