Fundamentals 10 min read

Understanding the Singleton Pattern in Java: Concepts, Comparisons, and Implementations

This article explains the Singleton design pattern in Java, outlines its characteristics, compares it with static classes, clarifies common misconceptions, and presents multiple thread‑safe and non‑thread‑safe implementation techniques with code examples.

Java Captain
Java Captain
Java Captain
Understanding the Singleton Pattern in Java: Concepts, Comparisons, and Implementations

1. What Is the Singleton Pattern

When a process requires that only one instance of a class exists at any time, the Singleton pattern is used to ensure a single, globally accessible object.

2. Characteristics of the Singleton Pattern

Only one instance can exist.

The singleton class creates its own unique instance.

The singleton instance is provided to other objects.

3. Singleton vs. Static Class

Although both provide a single access point, singletons can be inherited and overridden, support lazy loading, and are better suited for stateful resources such as DAO layers, whereas static classes are initialized once and have higher access efficiency.

Common Misconceptions About Static Classes

Misconception 1: Static methods always stay in memory while instance methods do not – actually, specially written instance methods can also be resident.

Misconception 2: Static methods reside on the heap and instance methods on the stack – both are loaded into a read‑only code memory area.

Choosing Between Static Class and Singleton

Use a static class when no state needs to be maintained; use a singleton when specific state must be preserved across the application.

4. Implementations of the Singleton Pattern

4.1 Lazy Initialization (Non‑Thread‑Safe)

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

This version creates the instance on first use but is not safe for concurrent access.

4.2 Thread‑Safe Lazy Initialization (Synchronized)

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

Adding synchronization makes it safe but incurs a performance penalty.

4.3 Eager Initialization (Thread‑Safe)

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

The instance is created when the class loads, eliminating lazy loading.

4.4 Static Inner‑Class Holder (Thread‑Safe, Lazy)

public class SingletonDemo {
    private static class SingletonHolder {
        private static final SingletonDemo INSTANCE = new SingletonDemo();
    }
    private SingletonDemo() {
        System.out.println("Singleton has loaded");
    }
    public static SingletonDemo getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

The inner static class is not loaded until getInstance() is called, providing lazy loading with thread safety.

4.5 Enum Singleton (Thread‑Safe)

enum SingletonDemo {
    INSTANCE;
    public void otherMethods() {
        System.out.println("Something");
    }
}

Recommended by Joshua Bloch, this approach guarantees a single instance, serialization safety, and protection against reflection.

4.6 Double‑Checked Locking (Usually Thread‑Safe)

public class SingletonDemo {
    private static volatile SingletonDemo instance;
    private SingletonDemo() {
        System.out.println("Singleton has loaded");
    }
    public static SingletonDemo getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo.class) {
                if (instance == null) {
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}

This pattern reduces synchronization overhead while preserving lazy loading.

4.7 Volatile‑Based Singleton

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

Marking the instance as volatile prevents instruction reordering that could expose a partially constructed object.

4.8 ThreadLocal Singleton (Thread‑Safe)

public class Singleton {
    private static final ThreadLocal<Singleton> tlSingleton = new ThreadLocal<Singleton>() {
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    };
    public static Singleton getInstance() {
        return tlSingleton.get();
    }
    private Singleton() {}
}

Each thread gets its own singleton instance, eliminating contention.

4.9 CAS‑Based Singleton (Thread‑Safe)

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();
    private Singleton() {}
    public static final Singleton getInstance() {
        for (;;) {
            Singleton current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new Singleton();
            if (INSTANCE.compareAndSet(null, current)) {
                return current;
            }
        }
    }
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

Uses AtomicReference and compare‑and‑set to achieve lock‑free thread safety.

Each of these implementations balances trade‑offs among lazy loading, performance, and thread safety, allowing developers to select the most appropriate version for their specific scenario.

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.

Javadesign patternlazy loadingSingleton
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

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.