The Singleton Pattern is a creational design pattern that ensures a class has only one instance while providing a global access point to that instance.
The Problem
Some things in a system should only exist once. For example, if you have a website connected to a database, you don't want to open a new connection every time a user clicks a button. Opening 1,000 connections would crash your server. Instead, you want one "Manager" that everyone shares.
The Solution: The Private Constructor
To prevent other classes from using the new keyword, we make the constructor private. Then, we create a static method that acts as a gatekeeper: it creates the instance if it doesn't exist, or returns the existing one if it does.
Implementation Styles in Java
1. Lazy Initialization (Thread-Safe)
This is the most common version. The instance is only created when it is actually needed. We use synchronized to make sure two different threads don't accidentally create two instances at the exact same time.
// DatabaseManager.java
public class DatabaseManager {
// 1. Static variable to hold the single instance
private static DatabaseManager instance;
private String connectionString;
// 2. Private constructor: No one else can call 'new DatabaseManager()'
private DatabaseManager() {
this.connectionString = "jdbc:mysql://localhost:3306/my_website";
System.out.println("Connecting to Database... (Heavy Operation)");
}
// 3. Global access point
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
instance = new DatabaseManager();
}
return instance;
}
public void query(String sql) {
System.out.println("Executing: " + sql + " on " + connectionString);
}
}
Full Code for Testing
This test proves that no matter how many times you "create" the object, you are actually looking at the exact same memory address.
// Save as SingletonTest.java
class ConfigurationSettings {
private static ConfigurationSettings instance;
private String theme = "Default";
private ConfigurationSettings() {
// Private constructor
}
public static synchronized ConfigurationSettings getInstance() {
if (instance == null) {
instance = new ConfigurationSettings();
}
return instance;
}
public void setTheme(String theme) {
this.theme = theme;
}
public String getTheme() {
return theme;
}
}
public class SingletonTest {
public static void main(String[] args) {
System.out.println("--- Singleton Pattern Test ---\n");
// Try to get the instance twice
ConfigurationSettings config1 = ConfigurationSettings.getInstance();
ConfigurationSettings config2 = ConfigurationSettings.getInstance();
// Change the theme using the first reference
config1.setTheme("Dark Mode");
// Check the theme using the second reference
System.out.println("Config 1 Theme: " + config1.getTheme());
System.out.println("Config 2 Theme: " + config2.getTheme());
// Verification: Are they the same object?
if (config1 == config2) {
System.out.println("\nSUCCESS: Both variables point to the same instance.");
} else {
System.out.println("\nFAILURE: Two different instances exist.");
}
}
}
Why use Singleton?
- Controlled Access: You have absolute control over how and when clients access the instance.
- Memory Savings: You avoid overhead from constant object creation and garbage collection.
- Global State: It’s a convenient way to store settings or logs that need to be accessed from many different parts of your code.
A Quick Warning (The "Anti-Pattern" Debate)
Singletons are powerful but can be overused. They make Unit Testing difficult because they carry state between tests. Only use them when you are absolutely sure that having more than one instance would break your system (like a Hardware Driver, a Logger, or a Database Pool).