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