# Thread-Safe Singleton Pattern in Java: 6 Implementation Methods Explained

> Explore 6 thread-safe Singleton patterns in Java. Learn about Enum, Static Holder, and Double-Checked Locking for robust, efficient, and secure singleton implementation.

- Repository: [CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes)
- Tags: deep-dive
- Published: 2026-02-24

---

**The most robust ways to implement a thread-safe Singleton pattern in Java include the Enum approach, Static Holder Class idiom, and Double-Checked Locking with `volatile`, each offering different trade-offs between laziness, performance, and protection against reflection attacks.**

The Singleton pattern guarantees that a class has only one instance and provides a global access point to it. In multithreaded environments, implementing a thread-safe Singleton pattern in Java requires careful handling to prevent race conditions during lazy initialization. The CyC2018/CS-Notes repository documents six distinct approaches in `notes/设计模式  - 单例.md`, ranging from the naïve unsafe version to the reflection-proof Enum singleton.

## 1. Lazy Initialization (Thread-Unsafe)

The simplest lazy implementation delays instantiation until the first call to `getUniqueInstance()`, but fails under concurrency.

```java
public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton() {}
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();   // ← race condition
        }
        return uniqueInstance;
    }
}

```

When two threads simultaneously find `uniqueInstance == null`, both proceed to create a new object, violating the singleton guarantee. As noted in the source analysis at [lines 23‑36][lazy-unsafe], this version is suitable only for single-threaded demos.

## 2. Eager Initialization (Static Field)

This approach creates the instance during class loading, eliminating synchronization concerns.

```java
public class Singleton {
    private static final Singleton uniqueInstance = new Singleton();
    private Singleton() {}
    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }
}

```

The JVM synchronizes class initialization, ensuring the instance is created exactly once before any thread accesses it ([lines 46‑48][eager]). The drawback is the lack of lazy initialization; the instance is created even if never used.

## 3. Synchronized Accessor Method

Adding `synchronized` to the getter prevents concurrent creation but introduces performance overhead.

```java
public static synchronized Singleton getUniqueInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

```

This guarantees thread safety by locking the entire method, as shown at [lines 56‑62][sync]. However, every call incurs synchronization overhead, even after the instance is initialized.

## 4. Double-Checked Locking (DCL)

Double-checked locking minimizes synchronization by checking the instance twice—once without locking and once within a synchronized block.

```java
public class Singleton {
    private volatile static Singleton uniqueInstance;
    private Singleton() {}
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

```

The `volatile` keyword is critical: it prevents instruction reordering that could expose a partially constructed object to other threads ([lines 71‑88][double-checked]). After initialization, calls incur only a cheap null check, making this the preferred high-performance lazy initialization approach.

## 5. Static Holder Class Idiom

This pattern leverages the JVM's class loading mechanism to achieve lazy initialization without explicit synchronization.

```java
public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}

```

The inner static class `SingletonHolder` is not loaded until `getUniqueInstance()` is invoked. Class loading is thread-safe, so the instance is created exactly once without `synchronized` or `volatile` ([lines 118‑130][holder]). This is widely considered the best lazy-initialization approach for most applications.

## 6. Enum Singleton

The Enum approach provides built-in thread safety, serialization safety, and protection against reflection attacks.

```java
public enum Singleton {
    INSTANCE;
    private String objName;
    public String getObjName() { return objName; }
    public void setObjName(String objName) { this.objName = objName; }
}

```

The JVM guarantees that enum values are instantiated only once. Serialization automatically returns the same instance, and reflection cannot invoke the private enum constructor ([lines 134‑152][enum]). This is the most robust implementation, recommended by Joshua Bloch in *Effective Java*.

## How to Choose the Right Implementation

| Situation | Recommended Pattern |
|-----------|----------------------|
| Simplicity, no lazy requirement | **Eager (static)** |
| Minimal lock overhead, lazy init needed | **Static holder** |
| Highest performance with lazy init, Java 5+ | **Double-checked locking** |
| Need absolute safety against reflection/serialization | **Enum** |
| Educational/demo of thread-unsafe case | **Lazy (unsafe)** |
| Quick prototype where lock cost is negligible | **Synchronized accessor** |

## Summary

- **Lazy (unsafe)**: Demonstrates the race condition risk in multithreaded environments.
- **Eager**: Simplest thread-safe approach, sacrificing lazy initialization.
- **Synchronized accessor**: Easy to implement but incurs unnecessary locking overhead.
- **Double-checked locking**: High-performance lazy initialization requiring `volatile` for memory visibility.
- **Static holder class**: Lazy initialization without explicit synchronization, leveraging JVM class loading guarantees.
- **Enum**: The most robust solution, automatically handling thread safety, serialization, and reflection attacks.

## Frequently Asked Questions

### Why is the `volatile` keyword necessary in double-checked locking?

Without `volatile`, the JVM may reorder the steps of object creation (allocate memory, initialize object, assign reference). Another thread could see a non-null reference to an incompletely initialized object. The `volatile` keyword ensures happens-before semantics, preventing this reordering and guaranteeing visibility across threads.

### Can reflection break Singleton patterns?

Yes, reflection can bypass private constructors in most Singleton implementations by calling `setAccessible(true)` and creating new instances. The **Enum** Singleton is immune to this attack because the JVM prevents reflection from instantiating enum constants, throwing an `IllegalArgumentException` if attempted.

### Which Singleton implementation is best for Android development?

The **Static Holder Class** idiom is generally preferred for Android because it provides lazy initialization without the synchronization overhead of double-checked locking or enum overhead. However, if you need protection against serialization or reflection, or if the Singleton holds state that must survive configuration changes, the **Enum** approach is safer despite its slightly higher memory footprint.

### How does the Enum Singleton prevent serialization attacks?

Standard serialization can create new instances of Serializable classes during deserialization. However, when an enum is deserialized, the JVM ensures that the same constant instance is returned rather than creating a new object. This is handled automatically by the `readObject` and `readResolve` mechanisms built into the enum type, making it inherently serialization-safe without boilerplate code.