# How to Implement a Thread-Safe Singleton with Double-Checked Locking in Java

> Implement a thread-safe Singleton in Java using double-checked locking. Learn how volatile and synchronized blocks ensure lazy initialization with minimal overhead. Optimize your concurrent code now.

- Repository: [Ilkka Seppälä/java-design-patterns](https://github.com/iluwatar/java-design-patterns)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Use a `volatile` instance field combined with two `null` checks—one outside and one inside a `synchronized` block—to lazily initialize a Singleton while minimizing synchronization overhead.**

The double-checked locking pattern is a widely used optimization for lazy singleton initialization in multithreaded environments. This article examines the production-ready implementation found in the `iluwatar/java-design-patterns` repository, demonstrating how to correctly apply thread-safe Singleton with double-checked locking in modern Java applications.

## Why Double-Checked Locking Matters

Without optimization, every call to `getInstance()` would require synchronization, creating a bottleneck under high concurrency. The double-checked locking pattern solves this by checking if the instance exists before entering a synchronized block, reducing synchronization overhead to the first initialization only.

## Core Implementation Details

The canonical implementation resides in [`singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java`](https://github.com/iluwatar/java-design-patterns/blob/main/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java). This class demonstrates the precise mechanics required for correct double-checked locking in Java.

### The Volatile Field Declaration

At line 39, the instance is declared with the `volatile` keyword:

```java
private static volatile ThreadSafeDoubleCheckLocking instance;

```

The `volatile` modifier ensures that writes to the instance field are immediately visible to all threads, preventing the instruction reordering issues that made double-checked locking broken in early Java versions.

### Local Variable Optimization

Following Joshua Bloch's guidance from *Effective Java*, the implementation uses a local variable at line 58 to cache the volatile field:

```java
ThreadSafeDoubleCheckLocking result = instance;

```

This technique avoids the performance cost of repeated volatile reads, providing approximately a 25% speed improvement over accessing the volatile field directly in multiple places.

### The Double-Check Logic

The method implements two distinct null checks. The first check at line 58 occurs outside any synchronized block:

```java
if (result == null) {

```

If the instance is null, the code enters a synchronized block at line 65 using the class object as the monitor:

```java
synchronized (ThreadSafeDoubleCheckLocking.class) {

```

Inside the synchronized block, a second null check at line 70 verifies that another thread did not initialize the instance while the current thread was waiting for the lock:

```java
if (instance == null) {
    instance = new ThreadSafeDoubleCheckLocking();
}

```

### Reflection Attack Protection

The private constructor at lines 44-46 includes a guard against reflection attacks:

```java
if (instance != null) {
    throw new IllegalStateException("Already initialized.");
}

```

If a malicious actor attempts to invoke the constructor via reflection after the singleton is initialized, the code throws an `IllegalStateException`, preventing multiple instantiation.

## Complete Implementation Example

Here is the full implementation based on the source code:

```java
package com.iluwatar.singleton;

public final class ThreadSafeDoubleCheckLocking {
    private static volatile ThreadSafeDoubleCheckLocking instance;
    
    private ThreadSafeDoubleCheckLocking() {
        if (instance != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }
    
    public static ThreadSafeDoubleCheckLocking getInstance() {
        ThreadSafeDoubleCheckLocking result = instance;
        if (result == null) {
            synchronized (ThreadSafeDoubleCheckLocking.class) {
                result = instance;
                if (result == null) {
                    instance = result = new ThreadSafeDoubleCheckLocking();
                }
            }
        }
        return result;
    }
}

```

## Thread Safety Verification

The repository includes comprehensive tests in [`singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java`](https://github.com/iluwatar/java-design-patterns/blob/main/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java). The test suite creates 10,000 `Callable` tasks that each invoke `getInstance()` on separate threads, verifying that every thread receives the identical instance reference. This empirical validation confirms that the double-checked locking implementation correctly handles high-concurrency scenarios without race conditions.

## Java Version Requirements

This implementation requires **Java 5 or later**. The `volatile` keyword in Java 5 received enhanced semantics that provide the necessary *happens-before* guarantees for safe double-checked locking. As noted in the source code comments at lines 30-33, this pattern is "Broken under Java 1.4" because earlier JVMs did not prevent instruction reordering of volatile writes.

## Summary

- Declare the singleton instance as `volatile` to ensure visibility across threads and prevent instruction reordering.
- Use a local variable to cache the volatile field, eliminating the performance penalty of repeated volatile reads.
- Implement two null checks: one outside the synchronized block for performance, and one inside to prevent race conditions during initialization.
- Guard the private constructor against reflection attacks by checking if the instance already exists.
- Requires Java 5+ due to the enhanced `volatile` semantics introduced in that version.

## Frequently Asked Questions

### What is double-checked locking?

Double-checked locking is an optimization pattern that reduces synchronization overhead by first testing the locking criterion without acquiring the lock. Only if the check indicates that locking is necessary does the code synchronize. In singleton implementations, this means checking if the instance is null before entering a synchronized block, then checking again inside the block to ensure thread-safe initialization.

### Why must the instance field be declared volatile?

The `volatile` keyword establishes a happens-before relationship between the write to the instance field and subsequent reads by other threads. Without `volatile`, the Java Memory Model might allow instruction reordering that could cause a thread to see a partially constructed object. The `volatile` modifier ensures that the singleton is fully constructed before any thread can access the reference, preventing the "unsafe publication" problem that made double-checked locking fail in Java 1.4.

### Does double-checked locking work in all Java versions?

No, double-checked locking only works reliably in Java 5 and later versions. Prior to Java 5, the `volatile` keyword did not provide sufficient memory visibility guarantees to prevent instruction reordering issues. The implementation in the `iluwatar/java-design-patterns` repository explicitly notes that it is "Broken under Java 1.4" and requires the enhanced `volatile` semantics introduced in Java 5 to function correctly.

### How does the implementation prevent reflection attacks?

The private constructor includes an explicit guard that checks whether the static `instance` field is already non-null. If a developer or attacker attempts to create a new instance via reflection after the singleton has been initialized, the constructor detects the existing instance and throws an `IllegalStateException` with the message "Already initialized." This prevents the creation of multiple instances even when the private constructor is made accessible through `setAccessible(true)`.