Master Singleton Classes in Java

Singleton classes are a fundamental design pattern in Java, used when you want to ensure that a class has only one instance and provide a global point of access to that instance. This pattern is invaluable when precisely one object is needed to coordinate actions across the system, such as a configuration manager, thread pool, or database connection. In this guide, we will explore various ways to create singleton classes in Java.

Classic Singleton

The classic singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. Here’s a classic example:

public class ClassicSingleton {
    // Private static instance variable
    private static ClassicSingleton instance;

    // Private constructor to prevent instantiation from other classes
    private ClassicSingleton() {
    }

    // Public static method to get the instance
    public static ClassicSingleton getInstance() {
        if (instance == null) {
            instance = new ClassicSingleton();
        }
        return instance;
    }
}

In this approach, the instance variable is private, and the only way to access it is through the getInstance() method, which creates the instance lazily the first time it’s called.

Thread-Safe Singleton

Ensuring thread safety is crucial when implementing a singleton class in a multi-threaded environment. Here are several thread-safe approaches:

Eager Initialization by Initializing Static Variables

the singleton instance is created when the class is loaded, rather than waiting until it’s needed. This approach is thread-safe because the instance is created before any potential concurrency issues can occur.

public class Singleton {
    // Private static instance variable
    private static Singleton instance = = new Singleton();

    // Private constructor to prevent instantiation from other classes
    private Singleton() {
    }

    // Public static method to get the instance
    public static Singleton getInstance() {
        return instance;
    }
}

Eager Initialization by Static Block

If you have complex preparation or check operations before initializing a singleton instance, you can use a static block instead.

public class Singleton {
    // Private static instance variable
    private static Singleton instance;

    static {
        // with preparation or check operations, then create new Singleton()
        instance = new Singleton();
    }

    // Private constructor to prevent instantiation from other classes
    private Singleton() {
    }

    // Public static method to get the instance
    public static Singleton getInstance() {
        return instance;
    }
}

Enum Singleton

Effective Java recommends using an enum to implement a singleton in Java. This approach is concise, provides inherent serialization, and protects against reflection attacks.

public enum EnumSingleton {
  INSTANCE;
  public void someMethod(String param) {
    // some class member
  }
}

Lazy Initialization using Double Checked Locking

Double-Checked Locking is a technique to achieve thread-safety and lazy initialization simultaneously. It checks if an instance exists and creates one only if needed.

public class ThreadSafeSingleton {
    private static volatile ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {
    }

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

The volatile keyword ensures that the instance variable is always read and written from the main memory, preventing visibility issues in multi-threaded environments.

Lazy Initialization using Holder Idiom

This approach leverages the Java class-loading mechanism to ensure thread-safety and lazy initialization.

public class HolderSingleton {
    private HolderSingleton() {
    }

    private static class SingletonHolder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }

    public static HolderSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

This idiom ensures that the instance is created only when getInstance() is called for the first time, and it’s guaranteed to be thread-safe due to class loading.

Eager Initialization vs Lazy Initialization

Lazy initialization is chosen when we are sure that we won’t always need this singleton instance. Eager initialization is better when we know we’ll always need.

Pros of Eager Initialization

  • The instance will be created when the application is started, there is no delay when using the object.
  • It works fine in a multi-threaded environment.

Cons of Eager Initialization

  • You might create an instance unnecessarily with this approach.

Pros of Lazy Initialization

  • The instance will be only created when needed, which saves memory if the instance object is pretty large.

Cons of Lazy Initialization

  • It needs synchronization to work in a multi-threaded environment to avoid race condition, which has slower performance.
  • There might be a significant delay when it takes time to finish creating the instance object.

Conclusion

Singleton classes in Java ensure that a class has only one instance and provide a global point of access to that instance. When implementing a singleton class, consider your application’s requirements and potential concurrency issues. Choose the approach that best suits your needs.