← Back to Advanced Java Course | Chapter 6: Design Patterns | Lesson 2 of 14

Java Singleton Pattern

Eager Initialization

Eager initialization creates the Singleton's one and only instance the moment the class is loaded by the JVM, regardless of whether it ends up being used. It's the simplest approach to implement, but its drawback is that it always pays the cost of instantiation even in a run where the singleton is never actually needed.

Example: Eager Initialization

java
public class Main {
	static class Singleton {
		static final Singleton INSTANCE = new Singleton(); // created at class-load time, whether used or not
		private Singleton() {}
	}
	public static void main(String[] args) {
		System.out.println(Singleton.INSTANCE == Singleton.INSTANCE);
	}
}

Lazy Initialization

Lazy initialization instead delays creating the instance until the first time it's actually requested, avoiding unnecessary work up front. To make lazy initialization safe under concurrent access, the creation logic needs to be wrapped in a synchronized method or block so two threads can't both create separate instances.

Example: Lazy Initialization

java
public class Main {
	static class Singleton {
		private static Singleton instance;
		private Singleton() {}
		static synchronized Singleton getInstance() {
			if (instance == null) instance = new Singleton(); // created only when first requested
			return instance;
		}
	}
	public static void main(String[] args) {
		System.out.println(Singleton.getInstance() == Singleton.getInstance());
	}
}

Double-Checked Locking

Double-checked locking reduces the overhead of synchronizing on every single access by checking whether the instance already exists twice: once before acquiring the lock, and once again after acquiring it, only creating the instance if it's still missing both times. The instance field must be declared volatile for this pattern to be safe, since without it, other threads could observe a partially-constructed object.

Example: Double-Checked Locking

java
public class Main {
	static class Singleton {
		private static volatile Singleton instance;
		private Singleton() {}
		static Singleton getInstance() {
			if (instance == null) {
				synchronized (Singleton.class) {
					if (instance == null) instance = new Singleton();
				}
			}
			return instance;
		}
	}
	public static void main(String[] args) {
		System.out.println(Singleton.getInstance() == Singleton.getInstance());
	}
}

Enum Singleton

Enum singletons get thread safety and protection against duplicate instantiation via serialization essentially for free, courtesy of guarantees baked into how the JVM handles enums. Many experienced Java developers consider this the simplest and most robust way to implement a Singleton in practice.

Example: Enum Singleton

java
public class Main {
	enum Singleton {
		INSTANCE;
		void doWork() { System.out.println("Working"); }
	}
	public static void main(String[] args) {
		Singleton.INSTANCE.doWork(); // thread-safe and serialization-safe for free
	}
}

Serialization & Reflection Protection

A conventional (non-enum) Singleton can accidentally end up duplicated during deserialization, or even via reflection forcibly calling a private constructor a second time. Implementing the readResolve() method lets you intercept deserialization and return the existing singleton instance instead of a new one.

Example: Serialization & Reflection Protection

java
import java.io.*;
public class Main {
	static class Singleton implements Serializable {
		static final Singleton INSTANCE = new Singleton();
		private Singleton() {}
		protected Object readResolve() {
			return INSTANCE; // returns the existing instance instead of a new one during deserialization
		}
	}
	public static void main(String[] args) {
		System.out.println(Singleton.INSTANCE);
	}
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.