The Singleton pattern ensures a class has only one instance and provides a global point of access to it. It is one of the most widely used — and misused — design patterns in software engineering. When applied correctly, it solves real problems around shared resource management, such as configuration settings, connection pools, and logging systems.
Imagine your application needs a centralized configuration manager that reads settings from a file or environment variables. If every part of your codebase creates its own instance, you end up with duplicated I/O, inconsistent state, and wasted memory. Worse, if one module changes a setting in its copy, other modules never see the update. You need a single, shared source of truth.
The Singleton pattern solves this by restricting instantiation to a single object. The class itself controls its own creation, stores the instance privately, and exposes it through a static access method. Every caller gets the same instance, guaranteeing consistent state across the entire application.
Controlled access to a sole instance. The class owns its lifecycle — no external code can create or destroy additional instances. This gives you a single coordination point without relying on global variables.
This example implements a thread-safe ConfigurationManager that loads application settings once and provides them everywhere.
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ConfigurationManager {
// volatile ensures visibility across threads
private static volatile ConfigurationManager instance;
private final Properties properties;
// Private constructor prevents external instantiation
private ConfigurationManager() {
properties = new Properties();
try (FileInputStream fis = new FileInputStream("app.properties")) {
properties.load(fis);
System.out.println("Configuration loaded from app.properties");
} catch (IOException e) {
System.err.println("Failed to load config: " + e.getMessage());
// Load defaults
properties.setProperty("db.host", "localhost");
properties.setProperty("db.port", "5432");
properties.setProperty("app.name", "MyApplication");
}
}
// Double-checked locking for thread safety
public static ConfigurationManager getInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = new ConfigurationManager();
}
}
}
return instance;
}
public String get(String key) {
return properties.getProperty(key);
}
public String get(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String value = properties.getProperty(key);
if (value == null) return defaultValue;
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return defaultValue;
}
}
}
// Usage
public class Application {
public static void main(String[] args) {
ConfigurationManager config = ConfigurationManager.getInstance();
String dbHost = config.get("db.host", "localhost");
int dbPort = config.getInt("db.port", 5432);
String appName = config.get("app.name", "DefaultApp");
System.out.println("App: " + appName);
System.out.println("DB: " + dbHost + ":" + dbPort);
// Same instance everywhere
ConfigurationManager sameConfig = ConfigurationManager.getInstance();
System.out.println("Same instance: " + (config == sameConfig)); // true
}
}
The same configuration manager scenario in Python, using a class-level instance with thread safety.
import threading
import json
import os
class ConfigurationManager:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
# Double-checked locking
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self._config = {}
self._load_config()
def _load_config(self):
config_path = "config.json"
if os.path.exists(config_path):
with open(config_path, "r") as f:
self._config = json.load(f)
print("Configuration loaded from config.json")
else:
# Load defaults
self._config = {
"db.host": "localhost",
"db.port": 5432,
"app.name": "MyApplication",
}
print("Loaded default configuration")
def get(self, key, default=None):
return self._config.get(key, default)
def get_int(self, key, default=0):
value = self._config.get(key)
if value is None:
return default
try:
return int(value)
except (ValueError, TypeError):
return default
# Usage
if __name__ == "__main__":
config = ConfigurationManager()
db_host = config.get("db.host", "localhost")
db_port = config.get_int("db.port", 5432)
app_name = config.get("app.name", "DefaultApp")
print(f"App: {app_name}")
print(f"DB: {db_host}:{db_port}")
# Same instance everywhere
same_config = ConfigurationManager()
print(f"Same instance: {config is same_config}") # True
Runtime.getRuntime() returns the single Runtime instance for the JVM.logging.getLogger(name) returns the same logger instance for a given name across the entire application.