Home/Design Patterns/Singleton Pattern

Singleton Pattern

🎯 Explain Like I'm 5...

Imagine your school can only have ONE principal at a time! 🏫 Even if you ask for the principal from the office, playground, or classroom, you always get the SAME principal!

🎓 The One Principal Rule:

  • There's only ONE principal in the entire school
  • Everyone who needs the principal talks to the SAME person
  • You can't create a new principal - the school already has one!

🚀 Why Is This Useful?

  • Some things should exist only ONCE (like a database connection or printer manager)
  • Everyone uses the same instance - no confusion!
  • Saves memory - why create multiple copies of the same thing?

🔧 Different Ways to Create Singleton

1. Eager Initialization

Create the instance right away when the class loads (simple but instance created even if not used)

EagerSingleton.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class EagerSingleton {
// Instance created at class loading time
private static final EagerSingleton instance = new EagerSingleton();
// Private constructor - no one can create new instance!
private EagerSingleton() {
System.out.println("Singleton instance created!");
}
// Global access point
public static EagerSingleton getInstance() {
return instance;
}
public void showMessage() {
System.out.println("Hello from Singleton!");
}
public static void main(String[] args) {
// Get the singleton instance
EagerSingleton singleton1 = EagerSingleton.getInstance();
EagerSingleton singleton2 = EagerSingleton.getInstance();
// Both variables point to SAME instance!
System.out.println(singleton1 == singleton2); // true
singleton1.showMessage();
}
}

2. Lazy Initialization

Create the instance only when needed (saves memory but not thread-safe)

LazySingleton.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class LazySingleton {
// Instance not created yet - null!
private static LazySingleton instance;
private LazySingleton() {
System.out.println("Lazy Singleton created!");
}
// Create instance only when first requested
public static LazySingleton getInstance() {
if (instance == null) {
// First time - create the instance
instance = new LazySingleton();
}
return instance;
}
public static void main(String[] args) {
System.out.println("Program started");
// Instance not created yet!
System.out.println("Getting instance for first time...");
LazySingleton s1 = LazySingleton.getInstance(); // Created here!
System.out.println("Getting instance again...");
LazySingleton s2 = LazySingleton.getInstance(); // Reuses existing
System.out.println(s1 == s2); // true
}
}

3. Thread-Safe Lazy Initialization

Safe for multiple threads but slower due to synchronization

ThreadSafeSingleton.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
// Synchronized method - only one thread at a time!
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
// Better approach: Double-Checked Locking
public static class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) { // First check - no locking
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) { // Second check - with locking
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
}

4. Bill Pugh Singleton (Best!)

Uses static inner class - thread-safe, lazy, and efficient!

BillPughSingleton.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class BillPughSingleton {
private BillPughSingleton() {}
// Static inner class - loaded only when getInstance() is called
private static class SingletonHelper {
// Instance created here - thread-safe by JVM!
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
public void doSomething() {
System.out.println("Singleton is working!");
}
public static void main(String[] args) {
BillPughSingleton singleton = BillPughSingleton.getInstance();
singleton.doSomething();
}
}
// This is the BEST way! Thread-safe, lazy, and efficient!

🌍 Real-World Examples

  • Database Connection Pool - Only one pool manages all connections
  • Logger - One logger writes to the same log file
  • Configuration Manager - One place for all app settings
  • Printer Spooler - One manager for all print jobs

When Should You Use Singleton?

  • Need exactly ONE instance of a class
  • Global access point required
  • Resource sharing (database, file, network)
  • Controlling concurrent access to shared resource

🔑 Key Points to Remember

  • 1️⃣Private constructor prevents creating new instances
  • 2️⃣Static method provides global access point
  • 3️⃣Thread safety is important in multi-threaded apps
  • 4️⃣Be careful with serialization - can break singleton
  • 5️⃣Consider using dependency injection instead in some cases

⚠️ Common Pitfalls

  • ⚠️Makes testing harder (global state)
  • ⚠️Can hide dependencies in code
  • ⚠️Reflection can break singleton
  • ⚠️Cloning can create multiple instances

💪 Practice Scenarios

  • Implement a thread-safe logger singleton
  • Create a configuration manager singleton
  • Build a database connection pool singleton
  • Design a cache manager singleton