Fundamentals 12 min read

Mastering the Singleton Pattern in Java: Eager, Lazy, Double‑Check, and More

Explore the GOF 23 design patterns with a deep dive into the Singleton pattern, covering its purpose, characteristics, comparison to static classes, and multiple Java implementations—including eager, lazy, double‑checked locking, volatile, static inner class, and enum approaches—plus Spring’s real‑world usage.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
Mastering the Singleton Pattern in Java: Eager, Lazy, Double‑Check, and More

What is GOF23

The book Design Patterns – Elements of Reusable Object‑Oriented Software introduced the concept of design patterns. Its four authors – Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides – are known as the Gang of Four (GoF). The book describes 23 design patterns, often referred to as GOF23.

Design Pattern Classification

Creational Patterns – focus on object creation, hiding the instantiation logic behind factory methods.

Structural Patterns – focus on class and object composition to obtain new capabilities.

Behavioral Patterns – focus on communication between objects.

Singleton Pattern

The Singleton pattern guarantees that a class has only one instance throughout the program, preventing duplicate object creation, reducing memory overhead and avoiding logical errors caused by multiple instances.

Key Characteristics of a Singleton

Only one instance can exist.

The class creates its own unique instance (private constructor).

A public method provides access to that instance.

Singleton vs. Static Class

Both provide a single point of access, but a Singleton retains object‑oriented benefits: it can be subclassed, implement interfaces, support lazy initialization, and hold state (e.g., configuration data). Static classes cannot be extended, lazily loaded, or hold instance state in the same way.

Implementation Techniques in Java

Hungry (Eager) Singleton

package com.secbro2.gof23.singleton;

public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();
    private HungrySingleton() {}
    public static HungrySingleton getInstance() { return instance; }
    public void helloSingleton() { System.out.println("Hello HungrySingleton!"); }
}

This creates the instance at class‑loading time, guaranteeing thread safety but allocating the object even if it is never used.

Lazy Singleton (Non‑Thread‑Safe)

package com.secbro2.gof23.singleton;

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
    public void helloSingleton() { System.out.println("Hello LazySingleton!"); }
}

Instance is created only when first requested, saving resources but unsafe in multithreaded environments.

Thread‑Safe Lazy Singleton (Synchronized)

package com.secbro2.gof23.singleton;

public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;
    private SynchronizedSingleton() {}
    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
    public void helloSingleton() { System.out.println("Hello SynchronizedSingleton!"); }
}

Synchronizing the whole method ensures thread safety but can become a performance bottleneck under high contention.

Double‑Checked Locking

package com.secbro2.gof23.singleton;

public class DCLSingleton {
    private static DCLSingleton instance;
    private DCLSingleton() {}
    public static DCLSingleton getInstance() {
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                if (instance == null) {
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
    public void helloSingleton() { System.out.println("Hello DCLSingleton!"); }
}

This reduces synchronization overhead by locking only during the first creation. Without volatile, instruction reordering may expose a partially constructed object.

Double‑Checked Locking with volatile

package com.secbro2.gof23.singleton;

public class VolatileDCLSingleton {
    private static volatile VolatileDCLSingleton instance;
    private VolatileDCLSingleton() {}
    public static VolatileDCLSingleton getInstance() {
        if (instance == null) {
            synchronized (VolatileDCLSingleton.class) {
                if (instance == null) {
                    instance = new VolatileDCLSingleton();
                }
            }
        }
        return instance;
    }
    public void helloSingleton() { System.out.println("Hello VolatileDCLSingleton!"); }
}

The volatile modifier prevents the JVM from reordering the instance‑creation steps, ensuring safe publication.

Static Inner‑Class Singleton

package com.secbro2.gof23.singleton;

public class InnerClassSingleton {
    private InnerClassSingleton() {}
    private static class Holder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    public static InnerClassSingleton getInstance() { return Holder.INSTANCE; }
    public void helloSingleton() { System.out.println("Hello InnerClassSingleton!"); }
}

The inner static class is not loaded until getInstance() is called, providing lazy initialization without explicit synchronization.

Enum Singleton

package com.secbro2.gof23.singleton;

public enum EnumSingleton {
    INSTANCE;
    public void helloSingleton() { System.out.println("Hello EnumSingleton!"); }
}

Effective Java recommends this approach because the enum guarantees thread safety, provides built‑in serialization support, and prevents multiple instantiation even via reflection.

Spring Framework Example

Spring’s AbstractBeanFactory uses a double‑checked locking pattern to manage bean singletons. The core method checks a cache, synchronizes on the cache when necessary, and creates the bean lazily.

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

This demonstrates a real‑world application of double‑checked locking within a popular framework.

Conclusion

For most scenarios the eager (hungry) implementation is the simplest and safest. When lazy initialization is required, use double‑checked locking with volatile, the static inner‑class technique, or the enum approach. The full source code is available at the following repository URL: https://github.com/secbr/gof23

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.

Design PatternsJavaspringenumthread safetySingleton
Senior Brother's Insights
Written by

Senior Brother's Insights

A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.

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.