I'm always excited to take on new projects and collaborate with innovative minds.
contact@niteshsynergy.com
https://www.niteshsynergy.com/
Welcome To Design Pattern Blog By Nitesh Synergy
A design pattern is a proven, reusable solution to a common problem in software design. It is not a complete code, but a template or guideline on how to solve specific design issues in object-oriented software.
Think of design patterns like blueprints—software architects use them to build robust, maintainable, and scalable applications.
Design patterns are broadly classified into 3 main categories:
Used when object creation is complex or needs to be controlled.
Pattern Name | Purpose |
---|---|
Singleton | Only one instance of a class. |
Factory Method | Create objects without exposing the exact class. |
Abstract Factory | Factory of related factories. |
Builder | Step-by-step object creation. |
Prototype | Clone existing objects. |
Singleton is a creational design pattern that ensures:
Use Singleton when:
Thread Pool
Avoid Singleton when:
Rules For Singleton Pattern:
Ensure the class constructor is private to prevent instantiation from outside the class.
Create a static (ideally final
) variable to hold the single instance of the class.
Provide a public static method or property that returns the same instance every time it's called.
In multithreaded environments, ensure that the instance creation logic is thread-safe to avoid multiple instances being created.
Override the cloning behavior to avoid duplication of the singleton object via clone()
.
Implement mechanisms (like readResolve()
in Java) to prevent deserialization from creating a new instance.
Add checks inside the constructor to throw an exception if an instance already exists, thus protecting against reflection-based instantiation.
Use a static block to eagerly initialize the singleton and include logic to prevent reflection-based violations if needed.
final
KeywordMark the instance variable as final
to ensure immutability and prevent reassignment after initialization.
✅Ways To Achieve Singleton Pattern:
Sol-1:
Basic Singleton Implementation (Eager Initialization):
public class Singleton {
private static final Singleton INSTANCE = new Singleton(); // Static instance
private Singleton() {
// Private constructor
}
public static Singleton getInstance() {
return INSTANCE; // Global access point
}
}
public class Singleton {
private static Singleton instance;
private Singleton() {
// Private constructor
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Sol-3:
Thread-Safe Singleton (Synchronized Method)
public class Singleton {
private static Singleton instance;
private Singleton() {
// Private constructor
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Sol-4:
Thread-Safe Singleton (Double-Checked Locking)
→ For all keywords -https://www.niteshsynergy.com/java
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// Private constructor
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
public class Singleton {
private static Singleton instance;
private Singleton() {
// Prevent Reflection from creating another instance
if (instance != null) {
throw new IllegalStateException("Singleton instance already created!");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Sol-6:
Singleton with Enum (Best Practice):
// Singleton Enum for Logging Utility
public enum Logger {
INSTANCE;
// Method to log messages
public void log(String message) {
System.out.println("LOG: " + message);
}
// Method to log errors
public void logError(String errorMessage) {
System.err.println("ERROR: " + errorMessage);
}
}
public class UserService {
// Some user-related logic
public void createUser(String username) {
// Log an informational message
Logger.INSTANCE.log("User creation started for: " + username);
try {
// Simulating user creation logic
if (username == null || username.isEmpty()) {
throw new IllegalArgumentException("Username cannot be null or empty");
}
// Simulate user created
Logger.INSTANCE.log("User created successfully: " + username);
} catch (Exception e) {
// Log the error using Singleton Logger
Logger.INSTANCE.logError("Error creating user: " + e.getMessage());
}
}
}
Sol-7:
Preventing Cloning via the clone() Method:
public class Singleton {
private static Singleton instance;
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Singleton instance already created!");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
@Override
public Object clone() {
throw new CloneNotSupportedException("Cloning of Singleton is not allowed!");
}
}
Sol-8:
Preventing Serialization Attack:
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton instance;
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Singleton instance already created!");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// Ensure Singleton instance is preserved during serialization/deserialization
protected Object readResolve() {
return instance;
}
}
Sol-9:
Using a Static Block (Eager Initialization with Reflection Safety):
public class Singleton {
private static final Singleton instance;
static {
try {
instance = new Singleton();
} catch (Exception e) {
throw new IllegalStateException("Singleton instance already created!");
}
}
private Singleton() {
// Prevent Reflection from creating another instance
if (instance != null) {
throw new IllegalStateException("Singleton instance already created!");
}
}
public static Singleton getInstance() {
return instance;
}
}
Sol-10:
Use of final Keyword:
public class Singleton {
private static final Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {
// Prevent Reflection from creating another instance
if (instance != null) {
throw new IllegalStateException("Singleton instance already created!");
}
}
public static Singleton getInstance() {
return instance;
}
}
→There are many other option to create Singleton class.
Below code for Singleton breaking ways:
//Code to break: 1. Reflection
class BreakWithReflection {
public static void breakWithReflection() {
try {
Singleton instance1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // Bypass private access
Singleton instance2 = constructor.newInstance();
System.out.println(" From breakWithReflection");
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//2. Serialization/Deserialization way break
class BreakWithSerialization {
public static void breakWithSerialization() throws Exception {
Singleton instance1 = Singleton.getInstance();
// Serialize instance
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
out.writeObject(instance1);
out.close();
// Deserialize instance
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println(" From breakWithSerialization");
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
// 3. Cloning way break
class BreakWithCloning {
public static void breakWithCloning() throws CloneNotSupportedException {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = (Singleton) instance1.clone(); // If clone() is accessible
System.out.println(" From breakWithCloning");
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
// 4. Multithreading (Race Condition)
class BreakWithMultithreading {
public static void breakWithMultithreading() {
Runnable task = () -> {
Singleton instance = Singleton.getInstance();
System.out.println(instance.hashCode());
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
System.out.println(" From BreakWithMultithreading");
thread1.start();
thread2.start();
}
}
//5. Garbage Collection
class BreakWithGarbageCollection {
public static void breakWithGarbageCollection() {
Singleton instance = Singleton.getInstance();
WeakReference<Singleton> weakRef = new WeakReference<>(instance);
instance = null; // Nullify the strong reference
System.gc(); // Force garbage collection
System.out.println(" From breakWithGarbageCollection");
Singleton newInstance = Singleton.getInstance();
System.out.println(weakRef.get() == newInstance); // False if GC removed the weak reference
}
}
//6. Subclassing
class BreakWithSubclassing {
public static void breakWithSubclassing() {
// Singleton subclassInstance = new Singleton() {}; // Anonymous subclass
// System.out.println(subclassInstance.hashCode());
}
}
class Singleton{
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// // If clone() is accessible, // 3. Cloning way break
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void showMessage() {
System.out.println("Singleton Instance");
}
}
package com.niteshsynergy.designPattern.CreationalDesign;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
public class Demo1 {
public static void main(String[] args) throws Exception {
/* Singleton s1 = Singleton.getInstance();
System.out.println(s1.hashCode());
Singleton s2 = Singleton.getInstance();
System.out.println(s2.hashCode());
*/
BreakWithReflection.breakWithReflection();
BreakWithSerialization.breakWithSerialization();
BreakWithCloning.breakWithCloning();
BreakWithMultithreading.breakWithMultithreading();
BreakWithGarbageCollection.breakWithGarbageCollection();
BreakWithSubclassing.breakWithSubclassing();
}
//Prevention Strategies:
//Prevent Reflection: Add a check in the private constructor.
/* private Singleton() {
if (instance != null) {
throw new IllegalStateException("Instance already created!");
}
}*/
//Serialization Protection: Implement readResolve().
/* protected Object readResolve() {
return instance;
}*/
//Prevent Cloning: Override clone() and throw an exception.
//java
//Copy code
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Cannot clone singleton");
}
//Thread-Safe Initialization: Use Bill Pugh Singleton or Double-Checked Locking.
//Final Class: Declare the class as final to prevent subclassing.
}
✅ Real-World Use Cases of Singleton Class
Use Case: Centralized Transaction Manager
🔹 Why Singleton?
Use Case: DatabaseConnectionManager Singleton
🔹 Why Singleton?
Use Case: GameSettings or GameEngine Singleton
🔹 Why Singleton?
Use Case: AIModelManager or APIClientManager
Imagine building an AI-based tool like ChatGPT:
🔹 Why Singleton?
Domain | Singleton Class Example | Purpose |
---|---|---|
Banking | TransactionManager | Maintain consistency and log transactions |
JDBC/Backend | DatabaseConnectionManager | Reuse DB connections, optimize resource usage |
Gaming | GameEngine / GameSettings | Global settings and logic across game screens |
AI Tools | AIModelManager / APIClient | Centralized model config and API management |
GameEngineSingleton.java
— Perfect Singleton for Game Engine