Fundamentals 10 min read

Java Singleton Pattern Implementations and Variants

This article explains four main categories of Java singleton implementations—eager (hungry), lazy (lazy), holder, and enum—detailing their basic forms, variations, thread‑safety characteristics, performance trade‑offs, and code examples, while also discussing pitfalls such as reflection and serialization.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Java Singleton Pattern Implementations and Variants

Eager (Hungry) Singleton

The eager (hungry) singleton creates the instance at class‑loading time, guaranteeing thread safety without synchronization but potentially wasting resources if the instance is never used.

public class Singleton2 {
  private static final Singleton2 singleton = new Singleton2();
  private Singleton2() {}
  public static Singleton2 getInstance() {
    return singleton;
  }
}

This approach is simple and fast in multithreaded environments, though it may allocate the object unnecessarily.

Lazy (Lazy) Singleton ("饱汉")

The lazy singleton defers instance creation until the first call to getInstance() , achieving lazy loading but requiring careful handling for thread safety.

public class Singleton1 {
  private static Singleton1 singleton = null;
  private Singleton1() {}
  public static Singleton1 getInstance() {
    if (singleton == null) {
      singleton = new Singleton1();
    }
    return singleton;
  }
}

This basic version is not thread‑safe; concurrent threads may create multiple instances.

Variant 1 – Synchronized Method

Adding the synchronized keyword to getInstance() makes the method thread‑safe but serializes all calls, hurting performance.

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

Variant 2 – Double‑Checked Locking (DCL) 1.0

This version adds an outer if check before entering a synchronized block, reducing the synchronization overhead.

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

Although it appears to provide lazy loading with better performance, the classic DCL is still unsafe on Java 1.8 without additional measures because of possible instruction reordering.

Variant 3 – DCL with volatile (DCL 2.0)

Marking the instance variable as volatile prevents reordering, making the double‑checked locking pattern safe for performance‑critical scenarios.

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

Holder Singleton

The holder pattern combines the lazy‑loading advantage of the lazy singleton with the thread‑safety of the eager one by using a static inner class to hold the instance.

public class Singleton3 {
  private static class SingletonHolder {
    private static final Singleton3 singleton = new Singleton3();
    private SingletonHolder() {}
  }
  private Singleton3() {}
  public static Singleton3 getInstance() {
    return SingletonHolder.singleton;
  }
}

This approach is widely recommended because it incurs no synchronization cost and still delays instance creation until first use.

Enum Singleton

Using an enum to implement a singleton is the most concise and inherently thread‑safe method; the JVM guarantees a single instance even against reflection and serialization attacks.

public enum Singleton4 {
  SINGLETON;
}

While extremely compact, the enum approach lacks a traditional getInstance() method and cannot provide lazy initialization.

Conclusion

All presented singleton implementations have trade‑offs. The eager, holder, and enum variants are thread‑safe by design, whereas the lazy variants require synchronization or volatile . Additionally, reflection and serialization can break most patterns, but the enum singleton remains immune to these attacks.

enumThread Safetydesign patternSingletonLazy Initialization
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.