One-line Definition
Encapsulates object instantiation to guarantee exactly one instance of a class exists system-wide.
Problem
Suppose your application manages a database connection pool, a logger, or a global configuration file. If every component instantiates its own database pool, you'll quickly exhaust database connections. The Singleton Pattern ensures all components share the exact same instance, coordinating access to the shared resource and preventing resource leaks.
Real-world Analogy
A country has exactly one president at a time. You don't create a new president every time you need a government decision — you go to the one who's already in office. The title "President" is the global access point, and the constitution ensures there's exactly one.
Structure
- Client
- Accesses the singleton instance via its public static method.
- Singleton
- Holds a private static reference to its own instance.
- Prevents external instantiation via a private constructor.
- Exposes a
getInstance()method.
Diagram
classDiagram
class Client {
}
class Singleton {
-static instance: Singleton
-Singleton()
+static getInstance(): Singleton
+doLogic()
}
Client --> Singleton : calls getInstance()
Code Walkthrough
Notice that DatabaseConnection strictly controls its own instantiation. Main (the client) cannot use new and must use the shared getInstance() method.
public class DatabaseConnection {
// Private constructor prevents external instantiation
private DatabaseConnection() {
System.out.println("Connecting to database...");
}
// Bill Pugh approach: static inner class for thread-safe lazy init
private static class Holder {
private static final DatabaseConnection INSTANCE = new DatabaseConnection();
}
public static DatabaseConnection getInstance() {
return Holder.INSTANCE;
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}
class Main {
public static void main(String[] args) {
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
db1.query("SELECT * FROM users");
System.out.println("Same instance? " + (db1 == db2)); // true
}
}
Bad vs Good
Bad Approach
Problems
- The constructor is public, allowing multiple instances.
- Simple lazy initialization without synchronization creates race conditions in multi-threaded environments.
public class Logger {
private static Logger instance;
public Logger() { // public constructor!
}
public static Logger getInstance() {
if (instance == null) { // Race condition!
instance = new Logger();
}
return instance;
}
}
Better Approach
Improvements
- Thread-safe without the overhead of
synchronizedon every call. - Private constructor completely prevents external instantiation.
public class Logger {
private Logger() {}
private static class Holder {
private static final Logger INSTANCE = new Logger();
}
public static Logger getInstance() {
return Holder.INSTANCE;
}
}
(Note: In modern Java, enum is often the safest way to implement Singletons, as it handles serialization and reflection attacks automatically).
Pros vs Cons
| Pros | Cons |
|---|---|
| Guarantees a single instance across the application | Hard to unit test because of tight coupling to the global state |
| Provides a global access point to a shared resource | Often misused as a glorified global variable |
| Lazy initialization saves resources if unused | Violates the Single Responsibility Principle (manages own lifecycle) |
| Avoids resource exhaustion (e.g., DB connections) | Makes dependency injection much harder |
| Thread-safe (when implemented correctly) | Can cause bottlenecks in concurrent environments |
When to Use
- Exactly one instance is needed to coordinate system-wide actions.
- You need a single pool of resources (threads, connections, caches).
- Global state must be strictly controlled and protected from concurrent modification.
- You require lazy initialization of a heavy shared resource.
When Not to Use
- You just want a convenient way to access a variable globally (use DI instead).
- The object holds state that shouldn't be shared across unrelated components.
- You are working in an environment with robust Dependency Injection (like Spring), which handles object lifecycles for you.
Real-world Examples
java.lang.Runtime#getRuntime()java.awt.Desktop#getDesktop()java.lang.System#getSecurityManager()- Spring Beans (Default scope is singleton)
Key Takeaway
The Singleton Pattern ensures strict control over a single shared resource by encapsulating its instantiation and lifecycle. While incredibly useful for connection pools or caches, it introduces global state and tight coupling. Avoid it unless you genuinely need to restrict instantiation, and prefer Dependency Injection frameworks where possible.