HuntExams Academy logo
JavaAdvanced#design-patterns

What is the Singleton pattern? How to implement it thread-safely?

Answer

Ensures only one instance per JVM. Thread-safe options: eager initialization, double-checked locking with volatile, or the enum singleton (recommended by Joshua Bloch — serialization-safe and reflection-safe).

Example

public enum Singleton {
  INSTANCE;
  public void doWork() { System.out.println("working"); }
}
Singleton.INSTANCE.doWork();

Detailed explanation

The Singleton pattern restricts a class to one instance and provides a controlled access point to it. It can suit a genuinely shared, stateless service, but should not become a default replacement for dependency injection. The usual phrase “one instance per JVM” needs qualification: Java class identity includes its defining class loader, so separate class loaders can each have their own singleton. Interviewers expect you to distinguish controlling instance creation from making the instance’s operations thread-safe; shared mutable fields still require appropriate synchronization.

Eager initialization is a straightforward thread-safe implementation: declare a private constructor, a private static final Singleton INSTANCE = new Singleton(), and a public static getInstance() method returning INSTANCE. The JVM’s class-initialization mechanism ensures that initialization completes safely before other threads use the initialized class. The instance is created when that class initializes, not necessarily when the JVM starts. For lazy creation without explicit locking, use the initialization-on-demand holder idiom: place INSTANCE inside a private static nested Holder class and return Holder.INSTANCE. The holder initializes only when first actively used.

Double-checked locking supports lazy initialization using a private static volatile Singleton instance. In getInstance(), first check whether instance is null; if so, synchronize on Singleton.class, check again, and create it only if still null. The second check prevents competing threads from creating duplicate instances after acquiring the lock. The volatile declaration is essential: it safely publishes the constructed object and prevents another thread from observing it through an unsafely published reference. This implementation is valid under the Java 5-and-later memory model; omitting volatile is a classic interview mistake.

For a simple singleton, Joshua Bloch recommends an enum: public enum Singleton { INSTANCE; public void doWork() { System.out.println("working"); } }. Call it using Singleton.INSTANCE.doWork(). Enum serialization preserves the identity of each constant, and standard reflective constructor invocation cannot create additional enum instances. Ordinary singleton classes need extra care around serialization, reflection, and cloning. Prefer the simplest suitable approach, and explain lifecycle, testing, and concurrency trade-offs rather than merely memorising syntax.

Key points to remember

  • A singleton provides one instance per defining class loader, not an unconditional JVM-wide guarantee.
  • Eager static initialization relies on JVM class-initialization guarantees for thread safety.
  • The static holder idiom provides lazy initialization without explicit synchronization.
  • Double-checked locking requires volatile, synchronization, and a second null check inside the lock.
  • An enum singleton preserves identity during serialization and blocks standard reflective construction.

Common mistakes candidates make

  • Using an unsynchronized lazy getter or omitting volatile from double-checked locking.
  • Assuming thread-safe construction automatically makes mutable singleton state thread-safe.
  • Claiming a private constructor alone prevents duplicates through serialization or reflection.

Likely follow-up questions

Why does double-checked locking need volatile?

Volatile safely publishes the instance reference so threads reading it see the constructor’s preceding writes. Synchronization prevents duplicate construction, but the fast-path read happens outside that lock and therefore needs the volatile guarantee.

Can a synchronized getInstance() method also work?

Yes: synchronizing the entire static getter makes a lazy null check and construction thread-safe, provided all access uses that getter. It acquires the class monitor on every call, whereas the holder idiom avoids explicit locking.

How can deserialization break a normal singleton?

Deserializing an ordinary Serializable singleton can create another instance without calling its normal constructor. A readResolve() method can return the canonical instance instead; enum serialization handles identity preservation automatically.

Interview tip: Define the pattern, show the enum implementation, then explain safe publication and the class-loader qualification.

Related Interview Questions

1
JavaIntermediate#design-patterns#spring

What is dependency injection?

Open
2
JavaBeginner#exceptions

What is the difference between throw and throws?

Open
3
JavaIntermediate#exceptions

Explain try-with-resources.

Open