Fundamentals 11 min read

Comprehensive Guide to the Singleton Pattern in Java: Implementations, Pitfalls, and the Secure Enum Solution

This article explains various Java singleton implementations—including eager, lazy, double‑checked locking, static inner class, and enum—demonstrates their thread‑safety issues, shows how reflection and serialization can break them, and presents the enum approach as a truly safe solution.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Comprehensive Guide to the Singleton Pattern in Java: Implementations, Pitfalls, and the Secure Enum Solution

Singleton is one of the simplest and most fundamental design patterns, yet its correct implementation in Java involves many subtle details that can affect thread safety and security.

Eager (Hungry) Singleton

The eager singleton creates the instance when the class is loaded, guaranteeing thread safety without synchronization.

public class SingletonHungry {
    private static SingletonHungry instance = new SingletonHungry();
    private SingletonHungry() {}
    private static SingletonHungry getInstance() {
        return instance;
    }
}

Test code runs multiple threads that call SingletonHungry.getInstance() and prints the same instance each time. The advantage is simplicity and thread safety; the drawback is that the instance is created even if never used, wasting memory.

Lazy Singleton

The lazy version delays instance creation until it is first requested, but the naïve implementation is not thread‑safe.

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

Running the same multithreaded test shows different objects being created, confirming the lack of thread safety.

Double‑Checked Locking (DCL)

DCL attempts to combine lazy initialization with thread safety by checking the instance twice, once before acquiring a lock and once inside the synchronized block.

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

Although it appears correct, the JVM may reorder the three steps of object creation (memory allocation, instance initialization, reference assignment), leading to a partially constructed object being visible to other threads. Adding volatile to the instance field prevents this reordering.

Static Inner Class Singleton

This technique leverages the class‑loading mechanism: the inner static class holds the instance, which is created only when the outer class’s getInstance() method is invoked.

public class SingleTonStaticInnerClass {
    private SingleTonStaticInnerClass() {}
    private static class HandlerInstance {
        private static SingleTonStaticInnerClass instance = new SingleTonStaticInnerClass();
    }
    public static SingleTonStaticInnerClass getInstance() {
        return HandlerInstance.instance;
    }
}

It provides lazy initialization without synchronization overhead and works across JDK versions, but it is still vulnerable to reflection attacks.

Unsafe Singletons: Reflection & Serialization Attacks

Even well‑written singletons can be broken via reflection by accessing private constructors, or via serialization by deserializing a previously written object, which creates a new instance.

// Reflection attack on DCL
Class<SingleTonDcl> clazz = SingleTonDcl.class;
Constructor<SingleTonDcl> ctor = clazz.getDeclaredConstructor();
ctor.setAccessible(true);
SingleTonDcl reflected = ctor.newInstance();
SingleTonDcl normal = SingleTonDcl.getInstance();
System.out.println(reflected);
System.out.println(normal);

Similarly, serializing SingletonHungry and then deserializing produces a distinct object unless readResolve() is overridden to return the original instance.

Enum‑Based Singleton (Truly Safe)

Using an enum guarantees a single instance, protects against reflection, serialization, and cloning.

public enum SingleTonEnum {
    /** instance */
    INSTANCE;
    public void doSomething() {
        System.out.println("doSomething");
    }
}

Attempting to create an enum instance via reflection throws Cannot reflectively create enum objects, confirming its robustness.

Conclusion

The article summarizes various singleton implementations, highlights common pitfalls such as thread‑safety issues, reflection, and serialization attacks, and recommends the enum approach as the only truly safe singleton pattern in Java.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaReflectionenumthread safetydesign patternSingleton
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.