CN

A Detailed Guide to Java Singleton Pattern

Word count: 1.5kReading time: 9 min
2015/05/06
loading

The Singleton pattern restricts how instances are created and accessed. This article follows eager initialization, double-checked locking, the holder idiom, and enums, explaining their initialization behavior and limits.

Technical review (2026-10-13): These patterns remain applicable. The discussion is checked against the Java 5+ memory model and Java SE 25 specifications. It corrects the loading/initialization distinction, runtime access checks, and the explanation of enum reflection. The code blocks named Singleton are alternatives, not declarations to combine in one source file.

What is the Singleton Pattern?

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

“One instance” needs a scope: the designated access path returns the same instance of the same runtime class. Different defining class loaders can create distinct classes with the same name, and separate processes do not share one static instance. Ordinary classes also need separate consideration of reflection and deserialization. See JVMS 5.3.

A singleton does not automatically make mutable state thread-safe or manage resource cleanup and lifetime.

Constructing the Singleton Pattern

Initialization timing gives us two broad approaches:

  1. Lazy initialization: defer creation until the instance is needed.
  2. Eager initialization: create the instance when the class holding the static field is initialized.

Loading, linking, and initialization are different phases. The static initializer below normally runs on the first active use of the class, such as calling getInstance(), not merely when its bytecode is loaded. See JLS 12.4.1.

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

private Singleton() {
}

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

Class initialization safely publishes this instance. If initialization is inexpensive and need not be deferred, this is already a simple solution.

For deferred creation, one option is double-checked locking (DCL):

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

private Singleton() {
}

public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
// Other threads may have passed the outer check before taking the lock.
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

The second check under the lock prevents duplicate concurrent creation. volatile safely publishes the reference together with preceding construction writes: a write to that volatile field happens-before subsequent reads of the same field. This is more precise than saying it freezes one instruction in place. The outer read holds no lock, so volatile must remain. See JLS 17.4.5.

This DCL form relies on the memory model in Java 5 / JDK 1.5 and later; adding volatile on an older JVM does not provide the same guarantee. See the JSR-133 authors’ FAQ. The example assumes the instance is never reset and this does not escape during construction. Subsequent mutations still need their own synchronization policy.

Another Singleton Pattern

The initialization-on-demand holder idiom is usually simpler than DCL:

1
2
3
4
5
6
7
8
9
10
11
12
public final class Singleton {
private Singleton() {
}

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

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

Initializing the outer Singleton class does not automatically initialize LazyHolder. The first read of LazyHolder.INSTANCE triggers holder initialization; the runtime coordinates competing threads and visibility. No extra volatile field is required. This comes from class-initialization semantics, not an incidental compiler optimization. See JLS 12.4.2.

The field can be private: access to a nested class’s private members is permitted within the same top-level class. Package access is not required. See JLS 6.6.1. The original holder-idiom discussion could not be retrieved during this review and remains a historical reference.

If static initialization fails, the class enters an erroneous state; subsequent initialization attempts may throw NoClassDefFoundError. A holder is not an automatic retry mechanism. Objects requiring external configuration, explicit retries, or replaceable lifetimes may need a different ownership design.

Using Enum

When one fixed enum constant fits the object, I recommend this simpler form, available since Java 5:

1
2
3
public enum Singleton {
INSTANCE;
}

Use Singleton.INSTANCE. The constant is created during enum-class initialization. An enum can implement interfaces but cannot extend another class, and mutable business fields are not automatically thread-safe.

Serialization of an ordinary singleton class

Adding serialization to a singleton can be a bit of a pain. The preceding ordinary classes do not implement Serializable, so Java object serialization cannot directly write them. If an ordinary singleton must support it, a readResolve() method can return the existing instance. Here is a separate complete example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.Serializable;

public final class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final SerializableSingleton INSTANCE =
new SerializableSingleton();

private SerializableSingleton() {
}

public static SerializableSingleton getInstance() {
return INSTANCE;
}

private Object readResolve() {
return INSTANCE;
}
}

readResolve() is recognized by serialization; it does not override an Object method, so do not add @Override here. It supplies a replacement after the object has been read, rather than preventing every temporary object from being created. Complex object graphs can retain references established before replacement, so this is not an absolute instance-control guarantee. See Serialization Specification 3.7.

Use transient according to whether a field belongs in serialized state and the object graph, not mechanically on every field because the class is a singleton. The stateless example above has no such instance fields.

Why Constructor.newInstance cannot create enum constants

I hadn’t looked deeply into reflection when I first wrote this. Reflection performs runtime access checks; private access is not enforced only at compile time. Where permitted, setAccessible(true) can suppress some checks; Java 9+ module boundaries may prevent access from being enabled. See AccessibleObject.

Enums have an additional rule: JLS 8.9 prohibits reflective enum instantiation, and Constructor.newInstance throws IllegalArgumentException for an enum constructor. This is not because every enum is abstract. The simple enum above is implicitly final; the abstract modifier on its java.lang.Enum base class does not automatically apply to the subclass.

What enum serialization preserves

Enums support serialization; they do not prohibit it. ObjectOutputStream uses special handling with the enum type descriptor and constant name, without serializing the constant’s instance-field values. ObjectInputStream resolves the existing constant using Enum.valueOf(enumType, name) instead of creating a new enum instance. See Serialization Specification 1.12.

The following identity comparison uses the enum version of Singleton. The helper requires Java 7+ for try-with-resources. Put the public types in separate Singleton.java and SingletonSerializationDemo.java files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public final class SingletonSerializationDemo {
public static void main(String[] args)
throws IOException, ClassNotFoundException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
out.writeObject(Singleton.INSTANCE);
}

Object restored;
try (ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(bytes.toByteArray()))) {
restored = in.readObject();
}
if (restored != Singleton.INSTANCE) {
throw new AssertionError("Enum identity was not preserved");
}
}
}

Under that contract, identity is preserved for the same runtime enum type. The example reads back its own in-memory data; it is not a snapshot of business state. Enum serialization ignores custom readObject, writeObject, and readResolve hooks.

A friendly reminder: do not casually rename or remove constants when existing serialized data must remain readable. If an old name is absent from the receiving enum, Java object deserialization fails; a direct Enum.valueOf() lookup of a missing name throws IllegalArgumentException. Reordering constants does not change name-based matching.

  1. Effective Java, second edition Item 71, “Use lazy initialization judiciously”; third edition Item 83 covers the same topic, with instance control in Item 89.
  2. Core Java, Volume I, volatile/concurrency chapters. The original “14.5.8” did not identify an edition and should not be assumed to match every edition.
  3. JLS 17.4.5: happens-before and JLS 12.4: initialization.
  4. JSR-133 authors’ FAQ on volatile and DCL across the Java 5 memory-model change. The old IBM developerWorks article URL now redirects to a Java category page and is obsolete as an article link.
  5. Double-Checked Locking and Lazy Initialization: historical translation, not retrievable during this review; use the specifications above for the technical guarantees.

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: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.

“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
    1. 4.1. Serialization of an ordinary singleton class
    2. 4.2. Why Constructor.newInstance cannot create enum constants
    3. 4.3. What enum serialization preserves
  5. 5. Recommended Reading:
  • About Me && Blog