Showing posts with label Design Pattern. Show all posts
Showing posts with label Design Pattern. Show all posts

June 13, 2025

๐Ÿงต Mastering Singleton Pattern in Java: volatile, synchronized, Spring Bean Scopes & Java 25 Best Practices

๐Ÿง  What is Singleton?

A Singleton ensures only one instance of a class is created and provides a global access point to it.

This is useful for:

  • Configuration classes

  • Logger objects

  • Database connection managers

  • Caching systems


๐Ÿ›‘ Problem: Thread-Unsafe Lazy Singleton

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton(); // ❌ Thread unsafe
        }
        return instance;
    }
}

This code may create multiple instances in a multithreaded environment.


✅ Proper Thread-Safe Singleton with volatile + synchronized

public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

⚙️ Modern Singleton Alternatives

๐Ÿ”น Static Holder Pattern

public class Singleton {
    private Singleton() {}
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

๐Ÿ”น Enum-based Singleton

public enum Singleton {
    INSTANCE;
}

๐ŸŒฟ Singleton in Spring Framework

Spring beans are Singleton by default, meaning:

Only one instance of the bean is created per Spring container.

✅ Declaring a Singleton Bean (default)

@Component
public class AppConfig {
    // default scope is Singleton
}

or explicitly:

@Component
@Scope("singleton")
public class AppConfig {
}

๐Ÿ”„ Changing Bean Scope in Spring

You can control a bean’s scope using the @Scope annotation.

Available Scopes in Spring (Core)

Scope Description
singleton One shared instance per Spring context (default)
prototype A new instance is created every time it's requested
request One instance per HTTP request (Web only)
session One instance per HTTP session (Web only)
application One instance per ServletContext (Web only)
websocket One instance per WebSocket session

๐Ÿ”ง How to Change Bean Scope

@Component
@Scope("prototype")
public class ReportGenerator {
    // A new instance is returned every time it's injected
}

Or using XML (for older Spring):

<bean id="myBean" class="com.example.MyBean" scope="prototype"/>

๐Ÿงช How to Control and Verify Scope

Example Test Class:

@SpringBootTest
public class ScopeTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void testScope() {
        MyBean b1 = context.getBean(MyBean.class);
        MyBean b2 = context.getBean(MyBean.class);

        System.out.println(b1 == b2 ? "Singleton" : "Prototype");
    }
}

๐Ÿ’Ž Java 25 Best Practices for Singleton

✅ Use volatile in lazy initialization
✅ Use @Scope("singleton") in Spring for explicit intent
✅ Use enum to prevent reflection/cloning
✅ Avoid heavy logic in constructor
✅ Override readResolve() if serializing
✅ Use @ThreadSafe for clarity
✅ Use JMH to benchmark Singleton performance
✅ Prefer DI-managed beans (Spring, Micronaut, etc.)
✅ Protect against classloader issues in plugins


๐Ÿ”š Conclusion

  • Use volatile and synchronized together in double-checked locking.

  • Use static inner class or enum for better control.

  • In Spring, prefer letting the container manage Singleton scope.

  • Change scope using @Scope, depending on your app needs.


✨ Summary Table

Singleton Pattern Thread-safe Lazy Init Recommended
static instance
synchronized method ⚠️
volatile + sync
Static holder ✅✅
Enum ✅✅ ✅✅✅
Spring Singleton Bean ✅✅✅

Would you like this exported as a Markdown file, HTML, or copy-ready for Medium/Dev.to?

May 10, 2025

๐ŸŒฟ Lazy Loading with Static Inner Class in Java — A Deep Dive Into Smart Initialization

In the world of Java performance optimization, lazy loading is a best practice when you're dealing with heavy objects or expensive computations that may not always be needed. One particularly elegant technique to achieve lazy initialization is the use of a static inner class — often underutilized, but incredibly powerful.

In this comprehensive blog, we’ll explore:

  • What lazy loading means in Java

  • How static inner classes work for lazy loading

  • Detailed comparisons with other lazy initialization techniques

  • When to use which method

  • Whether the static inner class approach is complex or not


๐Ÿ“˜ What Is Lazy Loading?

Lazy loading is a design pattern that defers the initialization of an object until it is actually needed.

Why Use Lazy Loading?

  • ๐Ÿง  Optimized memory usage: Don’t load it if you don’t use it.

  • ๐Ÿš€ Faster application startup: Defer costly operations.

  • ๐Ÿ›  Improved performance in scalable systems: Reduces bottlenecks during application bootstrapping.


๐Ÿงฑ Static Inner Classes for Lazy Initialization

What is a Static Inner Class?

A static inner class is a nested class declared with the static keyword. It does not need an instance of the outer class to be instantiated, and more importantly:

⚠️ A static inner class is not loaded into memory until it is explicitly referenced.

This makes it perfect for on-demand loading.


๐Ÿ›  Example: Singleton with Static Inner Class

public class LazySingleton {

    private LazySingleton() {
        System.out.println("Constructor called");
    }

    // Inner class loaded only when getInstance() is called
    private static class Holder {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }

    public static LazySingleton getInstance() {
        return Holder.INSTANCE;
    }
}

✅ JVM Guarantees:

  • The Holder class is only loaded when getInstance() is called.

  • Class loading in Java is thread-safe, so no synchronization is needed.

  • Lazy, thread-safe, and elegant — without boilerplate.


๐Ÿ” Other Ways to Do Lazy Initialization in Java

Let’s compare the static inner class technique with other common singleton approaches:

1. Eager Initialization

public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

✅ Simple
❌ Not lazy — instance is created even if never used


2. Lazy Initialization (Non-thread-safe)

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

✅ Lazy
❌ Not thread-safe — multiple threads may create multiple instances


3. Lazy Initialization with synchronized Method

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

✅ Lazy and thread-safe
❌ Performance overhead due to synchronization


4. Double-Checked Locking

public class LazySingleton {
    private static volatile LazySingleton instance;
    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

✅ Lazy
✅ Thread-safe
⚠️ Slightly complex
⚠️ Requires volatile (since Java 1.5) to prevent instruction reordering


5. Static Inner Class (Best of All Worlds)

✅ Lazy
✅ Thread-safe
✅ No synchronization overhead
✅ Cleaner and easier than double-checked locking
✅ JVM handles everything


๐Ÿ“Š Comparative Table

Method Lazy Thread-safe Performance Complexity
Eager Initialization Simple
Non-thread-safe Lazy Init Simple
Synchronized Method Simple
Double-Checked Locking Medium
Static Inner Class Very Simple

๐Ÿค” Is Static Inner Class Complicated?

Not at all! It's one of the simplest and safest lazy initialization patterns in Java:

  • No need for synchronization or volatile

  • No risk of race conditions

  • Easier to read and maintain than double-checked locking

In fact, many developers consider this the go-to method for implementing lazy singletons in modern Java applications.


๐Ÿง  When Should You Use It?

✅ Use static inner class lazy loading when:

  • You want a lazy singleton or lazy-loaded config

  • Thread safety is essential

  • You want cleaner code than double-checked locking

❌ Don’t use if:

  • Your object needs to be initialized early (eager loading is preferred)

  • You have complex dependency cycles that make static initialization risky


✅ Final Recommendation

If you're looking for a safe, performant, and clean way to implement lazy loading in Java — especially for singletons — use the static inner class approach. It's backed by the JVM, requires no locking or concurrency hacks, and works beautifully across all modern Java versions.


๐Ÿ“Œ TL;DR

  • Lazy loading improves performance and resource management.

  • Static inner classes defer loading until needed and are thread-safe by default.

  • Compared to other lazy initialization techniques, this is:

    • ๐Ÿ”’ Safer

    • ⚡ Faster

    • ๐Ÿงผ Cleaner


March 19, 2025

Singleton Design Pattern in Java - A Complete Guide

Introduction

The Singleton Pattern is one of the most commonly used design patterns in Java. It ensures that a class has only one instance and provides a global point of access to that instance. This pattern is particularly useful in scenarios where a single shared resource needs to be accessed, such as logging, database connections, or thread pools.

This guide covers everything you need to know about the Singleton Pattern, including:

  • Why we use it
  • How to implement it
  • Different ways to break it
  • How to prevent breaking it
  • Ensuring thread safety
  • Using Enum for Singleton
  • Best practices from Effective Java
  • Understanding volatile and its importance
  • Risks if Singleton is not implemented correctly
  • Reentrant use cases: Should we study them?

Why Use Singleton Pattern?

Use Cases:

  1. Configuration Management – Ensure that only one instance of configuration settings exists.
  2. Database Connection Pooling – Manage database connections efficiently.
  3. Caching – Maintain a single instance of cache to store frequently accessed data.
  4. Logging – Avoid creating multiple log instances and maintain a single log file.
  5. Thread Pools – Manage system performance by limiting thread creation.

What happens if we don't follow Singleton properly?

  • Memory Waste: Multiple instances can consume unnecessary memory.
  • Inconsistent State: If multiple instances manage shared data, inconsistency issues arise.
  • Performance Issues: Too many objects can slow down performance.
  • Thread Safety Problems: Without proper synchronization, race conditions can occur.

How to Implement Singleton Pattern

1. Eager Initialization (Simple but not memory efficient)

public class Singleton {
    private static final Singleton instance = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return instance;
    }
}

Pros:

  • Simple and thread-safe.

Cons:

  • Instance is created at class loading, even if not used, leading to unnecessary memory consumption.

2. Lazy Initialization (Thread unsafe version)

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Cons:

  • Not thread-safe. Multiple threads can create different instances.

3. Thread-safe Singleton Using Synchronized Method

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Cons:

  • Performance overhead due to method-level synchronization.

4. Thread-safe Singleton Using Double-Checked Locking

public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Why volatile is important?

  • Ensures visibility across threads.
  • Prevents instruction reordering by the compiler.
  • Avoids partially constructed instances being seen by other threads.

Pros:

  • Ensures lazy initialization.
  • Improves performance by synchronizing only when necessary.

5. Singleton Using Static Inner Class (Best Approach)

public class Singleton {
    private Singleton() {}
    
    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Pros:

  • Lazy initialization without synchronization overhead.
  • Thread-safe.

6. Enum Singleton (Recommended Approach - Effective Java Item 3)

public enum Singleton {
    INSTANCE;
    
    public void someMethod() {
        System.out.println("Singleton using Enum");
    }
}

Pros:

  • Enum ensures that only one instance is created.
  • Prevents breaking through Reflection, Cloning, and Serialization.
  • As recommended by Effective Java (Item 3), using an enum is the best way to implement a Singleton.

How to Break Singleton Pattern?

Even with careful implementation, Singleton can be broken using:

  1. Reflection:
    • Using Constructor.newInstance()
  2. Serialization & Deserialization:
    • Creating multiple instances when deserialized.
  3. Cloning:
    • Using clone() method to create a new instance.
  4. Multithreading Issues:
    • Poorly implemented Singleton might create multiple instances in concurrent environments.

How to Prevent Breaking Singleton?

1. Prevent Reflection Breaking Singleton

private Singleton() {
    if (instance != null) {
        throw new IllegalStateException("Instance already created");
    }
}

2. Prevent Serialization Breaking Singleton

protected Object readResolve() {
    return getInstance();
}

3. Prevent Cloning Breaking Singleton

@Override
protected Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException("Cloning not allowed");
}

4. Prevent Multithreading Issues

  • Use Enum Singleton as it is inherently thread-safe.

Reentrant Use Cases - Should We Study Them?

Reentrant Locks are useful when:

  • A thread needs to re-acquire the same lock it already holds.
  • Preventing deadlocks in recursive calls.

While Singleton itself does not directly relate to reentrant locks, studying reentrant locks can improve concurrency handling in Singleton implementations.


Best Practices for Singleton (Effective Java Item 3)

✔ Use Enum Singleton whenever possible. ✔ Use Static Inner Class if Enum cannot be used. ✔ Use Double-Checked Locking for thread-safe lazy initialization. ✔ Make the constructor private and prevent instantiation via Reflection. ✔ Implement readResolve() to prevent multiple instances in serialization. ✔ Override clone() to prevent instance duplication. ✔ Ensure volatile keyword is used for double-checked locking.


Conclusion

The Singleton Pattern is a powerful design pattern, but implementing it incorrectly can lead to serious issues. Among all implementations, Enum Singleton is the most robust and recommended approach as it prevents reflection, cloning, and serialization issues.

I hope this guide gives you a one-stop solution for Singleton in Java. Let me know in the comments if you have any questions! ๐Ÿš€

February 11, 2025

Is-A vs Has-A vs Association vs Composition vs Multiple & Multilevel Inheritance

When designing object-oriented systems, understanding relationships between classes is crucial. Terms like Is-A, Has-A, Association, Composition, Multiple Inheritance, and Multilevel Inheritance often create confusion. Whether you're a beginner or have 15 years of experience, this blog will provide a detailed yet easy-to-understand explanation with examples, along with insights into SOLID principles, Design Patterns from Head First Design Patterns, and best practices for future-proof design.




1. Is-A Relationship (Inheritance)

Definition:

An "Is-A" relationship is based on inheritance. It means one class is a subtype of another class, forming a hierarchy.

Example:

A Dog is an Animal, so it inherits behavior from Animal.

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound(); // Inherited from Animal
        dog.bark();      // Dog-specific behavior
    }
}

2. Has-A Relationship (Composition & Aggregation)

Definition:

A "Has-A" relationship means one class contains another class as a field, forming a dependency. This is called composition or aggregation.

Example 1 (Composition - Strong Relationship):

A Car has an Engine. The engine is tightly coupled with the car and cannot exist independently.

class Engine {
    void start() {
        System.out.println("Engine starts");
    }
}

class Car {
    private Engine engine;
    
    Car() {
        engine = new Engine(); // Strong relationship (Composition)
    }
    
    void startCar() {
        engine.start();
        System.out.println("Car is running");
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.startCar();
    }
}

Example 2 (Aggregation - Weak Relationship):

A Library has Books, but if the library is destroyed, books can still exist.

class Book {
    private String title;
    
    Book(String title) {
        this.title = title;
    }
    
    void display() {
        System.out.println("Book: " + title);
    }
}

class Library {
    private List<Book> books;
    
    Library(List<Book> books) {
        this.books = books; // Weak relationship (Aggregation)
    }
    
    void showBooks() {
        for (Book book : books) {
            book.display();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        List<Book> books = Arrays.asList(new Book("Head First Design Patterns"), new Book("Effective Java"));
        Library library = new Library(books);
        library.showBooks();
    }
}

3. Association (A General Relationship)

Definition:

Association is a general term for any relationship between two independent classes without ownership.

Example:

A Student and Teacher have an association.

class Student {
    private String name;
    
    Student(String name) {
        this.name = name;
    }
    
    void display() {
        System.out.println("Student: " + name);
    }
}

class Teacher {
    private String subject;
    
    Teacher(String subject) {
        this.subject = subject;
    }
    
    void teach(Student student) {
        System.out.println("Teacher teaching: " + subject);
        student.display();
    }
}

public class Main {
    public static void main(String[] args) {
        Student student = new Student("Alice");
        Teacher teacher = new Teacher("Math");
        
        teacher.teach(student);
    }
}

4. Multiple Inheritance (Interface-Based)

Definition:

Java does not support multiple inheritance with classes but allows it using interfaces.

Example:

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

class Duck implements Flyable, Swimmable {
    public void fly() {
        System.out.println("Duck can fly");
    }
    
    public void swim() {
        System.out.println("Duck can swim");
    }
}

public class Main {
    public static void main(String[] args) {
        Duck duck = new Duck();
        duck.fly();
        duck.swim();
    }
}

5. Multilevel Inheritance

Definition:

Multilevel inheritance forms a chain of inheritance.

Example:

class Animal {
    void eat() {
        System.out.println("Animal eats");
    }
}

class Mammal extends Animal {
    void walk() {
        System.out.println("Mammal walks");
    }
}

class Dog extends Mammal {
    void bark() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();
        dog.walk();
        dog.bark();
    }
}

Comparison Table

Concept Definition Example
Is-A (Inheritance) Class is a type of another class Dog extends Animal
Has-A (Composition) Class contains another class Car has an Engine
Aggregation Class contains another class with weak dependency Library has Books
Association General relationship without ownership Student and Teacher
Multiple Inheritance A class implements multiple interfaces Duck implements Flyable, Swimmable
Multilevel Inheritance A class extends another, forming a chain Dog extends Mammal extends Animal

Final Thoughts

Understanding these concepts is crucial for designing scalable, maintainable software. Applying these principles correctly will make you a better software architect.

What are your thoughts? Do you prefer Is-A or Has-A in your projects? Let me know in the comments!

February 6, 2025

How to Select the Right Design Pattern for Your Software

Design patterns are essential tools in software engineering that provide well-established solutions to common problems. Selecting the right design pattern ensures efficient, maintainable, and scalable code. But how do you decide which one to use? Let’s break it down in a structured, modern approach.

Identifying the Problem Domain

Before choosing a design pattern, identify the type of problem you are solving:

๐Ÿ”น Object Creation? → Use Creational Patterns
๐Ÿ”น Object Assembly? → Use Structural Patterns
๐Ÿ”น Object Interactions? → Use Behavioral Patterns

Creational Patterns (Managing Object Creation)

These patterns focus on the efficient creation of objects while keeping the system flexible and scalable.

Pattern When to Use Simple Story Related Patterns
Singleton Use when only one instance of a class should exist throughout the application. "A school has only one principal managing everything." Factory Method, Prototype
Factory Method Use when the creation process should be delegated to subclasses to maintain flexibility. "A bakery makes different types of cakes using different recipes." Abstract Factory, Builder
Abstract Factory Use when a family of related objects needs to be created without specifying their concrete types. "A car factory produces different models but follows the same process." Factory Method, Prototype
Prototype Use when creating objects is expensive, and cloning existing objects can improve performance. "A painter makes exact copies of their artwork instead of repainting it." Singleton, Builder
Builder Use when constructing a complex object requires step-by-step assembly. "Building a burger with different ingredients step by step." Factory Method, Prototype

Structural Patterns (Organizing Object Composition)

These patterns help structure classes and objects for flexibility and efficiency.

Pattern When to Use Simple Story Related Patterns
Adapter Use when you need to make two incompatible interfaces work together. "A travel adapter allows your charger to work in different countries." Bridge, Facade
Bridge Use when you want to separate abstraction from implementation for better scalability. "A remote control works with different TV brands." Adapter, Composite
Composite Use when you need to treat a group of objects as a single entity. "A tree consists of branches, and branches have leaves, but all are part of the tree." Decorator, Flyweight
Decorator Use when you need to add new behavior dynamically to an object. "Adding extra cheese and toppings to a pizza without changing its base." Composite, Proxy
Facade Use when you need to simplify interactions with a complex subsystem. "A hotel concierge handles all guest requests instead of them contacting each service separately." Adapter, Proxy
Flyweight Use when many objects share similar data, and memory optimization is crucial. "Instead of giving each student a textbook, they all share a library copy." Composite, Proxy
Proxy Use when controlling access to an object is needed, such as for security or caching. "A receptionist verifies visitors before letting them into an office." Decorator, Flyweight

Behavioral Patterns (Managing Object Interactions)

These patterns focus on how objects communicate and collaborate.

Pattern When to Use Simple Story Related Patterns
Observer Use when multiple objects need to be notified of state changes. "A YouTuber uploads a video, and all subscribers get notified." Mediator, Event-driven systems
Strategy Use when multiple algorithms should be interchangeable dynamically. "A game character can switch between running, walking, and swimming modes." State, Template Method
Command Use when you need to encapsulate requests as objects for undo/redo operations. "A TV remote records previous actions, so you can undo a channel change." Chain of Responsibility, Mediator
State Use when an object needs to change behavior dynamically based on internal state. "A traffic light changes behavior based on the current light color." Strategy, Observer
Visitor Use when new operations need to be added to an object structure without modifying it. "A tour guide explains different exhibits without changing the museum setup." Composite, Iterator
Memento Use when object states need to be captured and restored without exposing internal details. "A video game lets players save and load progress at any time." Command, State
Iterator Use when sequential access is needed for a collection without exposing its structure. "Flipping through the pages of a book one by one." Composite, Visitor
Mediator Use when complex communications between objects should be centralized. "An air traffic controller manages communication between multiple pilots." Observer, Chain of Responsibility
Chain of Responsibility Use when a request needs to be processed by multiple handlers in sequence. "A customer service call passes through different departments before getting solved." Command, Mediator
Template Method Use when the structure of an algorithm should be defined, but steps should be customized. "A recipe provides basic steps, but ingredients can be changed." Strategy, Factory Method

Modern Approach to Choosing a Design Pattern

๐Ÿ”น Scalability Concern? → Use Singleton, Factory, or Prototype to manage object creation efficiently.
๐Ÿ”น Code Readability? → Use Facade or Adapter to simplify interfaces.
๐Ÿ”น Extensibility? → Use Decorator or Strategy to add functionality dynamically.
๐Ÿ”น Performance Optimization? → Use Flyweight or Proxy to reduce memory usage and improve speed.
๐Ÿ”น Flexible Communication? → Use Observer, Mediator, or Chain of Responsibility to handle dynamic interactions.

Final Thoughts

Choosing the right design pattern depends on your specific problem and project needs. By understanding the problem domain and applying the right pattern, you can build more maintainable and scalable software. Keep experimenting with different patterns to refine your approach and create robust, efficient applications!

January 16, 2025

Creating a Singleton Class in Java

 Singleton design pattern is a widely used pattern in Java and other object-oriented programming languages. It ensures that a class has only one instance and provides a global access point to that instance. This article explores how to create a singleton class in Java, discusses best practices, and highlights common mistakes to avoid.


What is a Singleton Class?

A Singleton class restricts the instantiation of a class to one single instance. This pattern is often used for scenarios such as:

  • Resource Management: Managing connections, logging, or thread pools.

  • Shared Configuration: Providing a single access point for application-wide configurations.

  • Caching: Storing frequently used data to reduce computation or database access.


Steps to Create a Singleton Class in Java

1. Private Constructor

Ensure the class constructor is private so that no other class can instantiate it.

2. Static Instance Variable

Declare a static variable to hold the single instance of the class.

3. Public Access Method

Provide a public static method that returns the instance of the class.


Example Implementations

Eager Initialization

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
        // Private constructor
    }

    public static Singleton getInstance() {
        return instance;
    }
}

Pros: Simple to implement. Cons: Instance is created even if it’s never used, leading to potential resource wastage.

Lazy Initialization

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // Private constructor
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Pros: Instance is created only when needed. Cons: Not thread-safe.

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;
    }
}

Pros: Thread-safe. Cons: Synchronized method can impact performance.

Double-Checked Locking

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;
    }
}

Pros: Efficient and thread-safe. Cons: Slightly complex to implement.

Enum Singleton

public enum Singleton {
    INSTANCE;

    public void someMethod() {
        // Business logic
    }
}

Pros: Simple, thread-safe, and prevents multiple instances even during serialization. Cons: Not flexible if your singleton class needs to extend another class.


Best Practices

  1. Lazy Initialization with Thread-Safety: Use double-checked locking or other efficient thread-safe approaches.

  2. Serialization Safe: Ensure the singleton remains singleton during serialization by overriding readResolve method.

    private Object readResolve() {
        return getInstance();
    }
  3. Avoid Reflection: Prevent instantiation via reflection by throwing an exception in the constructor if an instance already exists.

    private Singleton() {
        if (instance != null) {
            throw new IllegalStateException("Instance already exists!");
        }
    }
  4. Enum Singleton: Use enum whenever possible for simplicity and robustness.


Common Mistakes

  1. Non-Thread-Safe Lazy Initialization: Without synchronization, multiple threads can create separate instances.

  2. Reflection Issues: Singleton can be broken by reflection unless additional checks are implemented.

  3. Serialization Pitfalls: Without readResolve, deserialization can create a new instance.

  4. Improper Usage: Overusing singleton for unrelated scenarios can lead to tightly coupled code.


Performance Comparison

MethodThread-SafePerformanceUse Case
Eager InitializationYesHigh (no overhead)When instance creation is cheap.
Lazy InitializationNoHigh (no overhead)Single-threaded environments.
Synchronized MethodYesMedium (synchronization cost)Simple thread-safe requirements.
Double-Checked LockingYesHighEfficient and scalable.
Enum SingletonYesHighSerialization-safe and robust.

Latest Updates in Java 21 and Beyond

Java 21 introduces exciting features and enhancements that improve productivity and application performance:

  1. Pattern Matching for Switch (Finalized): Simplifies complex conditional logic with powerful type-safe patterns.

  2. Record Patterns: Enables pattern matching for records, further enhancing data decomposition.

  3. Scoped Values (Preview): Provides an efficient way to share immutable data across threads.

  4. String Templates (Preview): Simplifies the creation of dynamic strings while maintaining readability and security.

  5. Virtual Threads (Finalized): Revolutionizes thread management, offering lightweight and efficient threading for high-concurrency applications.

  6. Sequenced Collections: Introduces ordered collections for easier iteration and predictable behavior.

  7. Deprecations and Removals: Outdated methods and features have been removed, ensuring the language stays modern and concise.


What Next to Read?

To deepen your understanding, explore:

  • "Java Concurrency in Practice" by Brian Goetz for threading and concurrency.

  • "Effective Java" by Joshua Bloch for best practices and design patterns.

  • Official Java documentation and migration guides for Java 21.

Happy coding!Conclusion

The Singleton pattern is a powerful design tool in Java, but it must be implemented with care to avoid common pitfalls. By understanding the various implementation methods, their trade-offs, and best practices, you can create efficient, thread-safe singletons tailored to your project’s needs.

How have you used the Singleton pattern in your projects? Share your thoughts and experiences in the comments!