Java 4 min read

Singleton Design Pattern in Java: Part 1

Deep dive into the Singleton design pattern in Java. Learn different implementation approaches including eager initialization and lazy loading.

MR

Moshiour Rahman

Advertisement

What is the Singleton Pattern?

According to Wikipedia:

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance.

In simple terms, we use the Singleton pattern when we want only one instance of a class to exist throughout the application. It’s part of the Creational design patterns, as it deals with object creation.

Real-World Use Cases

Spring Framework

The Spring Framework uses the Singleton pattern extensively. Singleton is the default bean scope in the Spring container, meaning only one instance of each bean class is created per container.

Java Runtime

java.lang.Runtime is a classic example. Every Java application has a single instance of the Runtime class, accessed via getRuntime():

Runtime runtime = Runtime.getRuntime();
System.out.println("Available processors: " + runtime.availableProcessors());
System.out.println("Free memory: " + runtime.freeMemory());

Implementation Approaches

There are several ways to implement Singleton in Java:

  1. Declare a private constructor to prevent external instantiation
  2. Create the instance via a static field, static block, or static method

Important: Implementing thread-safe singletons is tricky in concurrent environments.

1. Eager Initialization

The simplest approach - create the instance at class loading time:

public class EagerSingleton {

    // Instance created at class loading
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    // Private constructor prevents instantiation
    private EagerSingleton() {}

    // Public access point
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

Pros:

  • Simple and thread-safe (JVM handles class loading synchronization)
  • No synchronization overhead

Cons:

  • Instance is created even if never used
  • Cannot handle exceptions during construction
  • May impact startup time if initialization is expensive

2. Static Block Initialization

Similar to eager initialization, but allows exception handling:

public class StaticBlockSingleton {

    private static final StaticBlockSingleton INSTANCE;

    // Static block for complex initialization
    static {
        try {
            // Add validation or configuration logic here
            INSTANCE = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Failed to create singleton", e);
        }
    }

    private StaticBlockSingleton() {}

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

Pros:

  • Exception handling during initialization
  • Still thread-safe via class loading

Cons:

  • Instance still created at class loading time

3. Lazy Initialization (Not Thread-Safe)

Create the instance only when first requested:

public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

Problem: This is NOT thread-safe!

If two threads call getInstance() simultaneously when instance is null, both might create separate instances.

// Thread 1: checks instance == null (true)
// Thread 2: checks instance == null (true)
// Thread 1: creates new instance
// Thread 2: creates another new instance <- BUG!

4. Synchronized Lazy Initialization

Add synchronized to ensure thread safety:

public class SynchronizedSingleton {

    private static SynchronizedSingleton instance;

    private SynchronizedSingleton() {}

    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

How it works:

The synchronized keyword ensures only one thread can execute getInstance() at a time, preventing race conditions.

Pros:

  • Thread-safe
  • Lazy initialization

Cons:

  • Performance impact: Every call to getInstance() requires synchronization
  • Reads cannot happen in parallel
  • Once initialized, synchronization is unnecessary overhead

Performance Problem

With the synchronized approach:

// Even for simple reads after initialization:
SynchronizedSingleton.getInstance().doSomething();  // Acquires lock
SynchronizedSingleton.getInstance().doSomething();  // Acquires lock again

This synchronization overhead occurs on every call, even though it’s only needed during the first initialization.

Coming in Part 2

In the next article, we’ll explore solutions to this performance problem:

  • Double-Checked Locking - Check before and after synchronization
  • Bill Pugh Singleton - Using static inner class
  • Enum Singleton - The recommended approach by Joshua Bloch

We’ll also cover:

  • How to prevent singleton breaking via reflection
  • How to handle serialization/deserialization
  • Thread-safe lazy initialization without synchronization overhead

Summary

ApproachThread-SafeLazyPerformance
EagerYesNoGood
Static BlockYesNoGood
Lazy (no sync)NoYesGood
SynchronizedYesYesPoor

Key Takeaways:

  • Use eager initialization for simple cases
  • Avoid basic lazy initialization in multi-threaded environments
  • Synchronized method has significant performance overhead
  • Better solutions exist (covered in Part 2)

Stay tuned for Part 2 where we’ll explore more efficient thread-safe approaches!

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.