Skip to main content

The Singleton Pattern

Category: Creational

The Singleton pattern ensures that a class has only one single instance throughout the entire lifecycle of an application, and it provides a global point of access to that instance.

This pattern is highly useful for centralized management classes, such as a DatabaseConnectionManager, a Logger, or a global Configuration object, where instantiating multiple copies would cause issues (e.g., locking a file twice, or wasting memory).


The Rules of a Singleton

To create a standard Singleton in Java, you must do two things:

  1. Make the constructor private so no other class can use the new keyword to instantiate it.
  2. Provide a public static method that returns the single instance of the class.

1. Eager Initialization

In eager initialization, the instance of the Singleton class is created at the time of class loading. This is the easiest method, but it has a drawback: the instance is created even if the client application never ends up using it, which could waste memory.

public class EagerSingleton {

// 1. Create the single instance immediately when the class loads
private static final EagerSingleton instance = new EagerSingleton();

// 2. Private constructor prevents instantiation from other classes
private EagerSingleton() {
System.out.println("Eager Singleton Created!");
}

// 3. Global access point
public static EagerSingleton getInstance() {
return instance;
}
}

2. Lazy Initialization (Not Thread-Safe)

To avoid wasting memory, we can use Lazy Initialization. The instance is only created the first time someone actually calls getInstance().

public class LazySingleton {

private static LazySingleton instance;

private LazySingleton() {}

public static LazySingleton getInstance() {
// Only create it if it doesn't exist yet!
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

[!WARNING] While this works in a single-threaded environment, it is highly dangerous in a multithreaded application. If two threads enter the if (instance == null) block at the exact same millisecond, they will both create a new instance, completely breaking the Singleton rule!


3. Double-Checked Locking (Industry Standard)

To make lazy initialization thread-safe, we must use the synchronized keyword. However, synchronizing the entire getInstance() method is terrible for performance, because every subsequent call to get the instance will be blocked, even after it's been created.

The solution is Double-Checked Locking. We check if it's null, synchronize, and then check if it's null again inside the synchronized block.

public class ThreadSafeSingleton {

// The 'volatile' keyword ensures that multiple threads handle the
// instance variable correctly when it is being initialized.
private static volatile ThreadSafeSingleton instance;

private ThreadSafeSingleton() {}

public static ThreadSafeSingleton getInstance() {
// First check (no locking, fast performance for subsequent calls)
if (instance == null) {
// Lock the class block
synchronized (ThreadSafeSingleton.class) {
// Second check (inside the lock)
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}

[!TIP] Modern Java Alternative: You can also implement a highly secure, thread-safe Singleton using a Java Enum! Because Java guarantees that Enum values are instantiated only once in a Java program, simply writing public enum Singleton { INSTANCE; } is considered an advanced best practice.