ProgramGuru

Singleton Class

Singleton Class

A school program needs reliable data and clear rules. A singleton design provides one shared instance through controlled access. This can represent a process-wide school configuration, but shared state still needs careful design.

  1. Understand

    A singleton design provides one shared instance through controlled access.

  2. Apply

    Trace the Java example, predict its output and explain the result before running it.

  3. Avoid mistakes

    A simple lazy singleton is not automatically thread-safe. An enum instance is a concise Java option; one instance does not imply immutable state.

Singleton Class

In Java, a Singleton class is a class that allows only one instance of itself to be created—and gives access to that single instance. It’s a widely used design pattern, especially when you need exactly one object to coordinate actions across the system.

Why Use a Singleton?

Imagine you're logging actions throughout your application. Would you want multiple loggers writing randomly? Of course not. A singleton logger ensures consistent behavior and state—no matter where it’s used.

  • Ensures controlled access to shared resources
  • Saves memory by avoiding multiple instances
  • Promotes centralized management (e.g., database connectors, config managers)

How to Implement a Singleton Class in Java

Let’s go through different ways to implement a singleton class. Each method has its own nuances, especially in multi-threaded environments.

1. Eager Initialization

The instance is created at the time of class loading.

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

    private EagerSingleton() {
        System.out.println("Instance created");
    }

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

Usage

public class Main {
    public static void main(String[] args) {
        EagerSingleton obj1 = EagerSingleton.getInstance();
        EagerSingleton obj2 = EagerSingleton.getInstance();
        System.out.println(obj1 == obj2); // true
    }
}
Instance created
true

Explanation

Only one object is created. Even if we try to get the instance multiple times, we get the same reference.

2. Lazy Initialization

In this approach, the instance is created only when it’s needed.

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
            System.out.println("Instance created");
        }
        return instance;
    }
}

Usage

public class Main {
    public static void main(String[] args) {
        LazySingleton obj1 = LazySingleton.getInstance();
        LazySingleton obj2 = LazySingleton.getInstance();
        System.out.println(obj1 == obj2); // true
    }
}
Instance created
true

Explanation

The object is created only when getInstance() is called the first time. It saves resources but is not thread-safe.

3. Thread-Safe Singleton (Synchronized Method)

This method ensures thread safety using synchronization.

public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
            System.out.println("Instance created");
        }
        return instance;
    }
}

Explanation

The synchronized keyword ensures that only one thread can execute this method at a time. It's safe, but might reduce performance due to synchronization overhead.

4. Double-Checked Locking (Efficient Thread-Safety)

This is a more efficient way of making the singleton thread-safe.

public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;

    private DoubleCheckedSingleton() {}

    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                    System.out.println("Instance created");
                }
            }
        }
        return instance;
    }
}

5. Singleton Using Enum (Best Practice)

Using Enum is the simplest and most effective way to implement a singleton in Java. It prevents reflection and serialization issues.

public enum EnumSingleton {
    INSTANCE;

    public void show() {
        System.out.println("Singleton using Enum");
    }
}

Usage

public class Main {
    public static void main(String[] args) {
        EnumSingleton singleton = EnumSingleton.INSTANCE;
        singleton.show();
    }
}
Singleton using Enum

Common Mistakes to Avoid

  • Leaving constructor public or protected
  • Ignoring thread safety in multi-threaded applications
  • Using reflection to break singleton (use Enum to prevent this)

Where is Singleton Used?

Singleton pattern is used across frameworks and applications:

  • Database connections
  • Logger classes
  • Configuration managers
  • File handlers

Important boundaries and mistakes

A simple lazy singleton is not automatically thread-safe. An enum instance is a concise Java option; one instance does not imply immutable state.

Try the school example

Before running this Java program, write down each line you expect to see. Identify the value, branch, object or exception responsible for each line. Then explain whether the following assumption is valid: A simple lazy singleton is not automatically thread-safe.

Java 17 · Main.java · complete practice program

public class Main  {
  enum SchoolConfig  {
    INSTANCE
  }
  public static void main(String[] args)  {
    System.out.println(SchoolConfig.INSTANCE == SchoolConfig.INSTANCE);
  }
}

Now change one thing: Compare the enum instance with itself again. Predict the new behavior before checking the worked variation.

Worked answer and reasoning

Java 17 · Main.java · source repeated for this answer

public class Main  {
  enum SchoolConfig  {
    INSTANCE
  }
  public static void main(String[] args)  {
    System.out.println(SchoolConfig.INSTANCE == SchoolConfig.INSTANCE);
  }
}

Expected output

true

A singleton design provides one shared instance through controlled access. This can represent a process-wide school configuration, but shared state still needs careful design.

A simple lazy singleton is not automatically thread-safe. An enum instance is a concise Java option; one instance does not imply immutable state.

Run with javac Main.java, then java Main. The declaration of Main names the class; main starts the example, and println writes a complete output line.

Worked variation: Compare the enum instance with itself again. Both references still identify the same instance.

Check your understanding

For the complete practice program in this lesson, which output is expected on Java 17?
Choose one answer. You can try again.

Answer and explanation

true

A singleton design provides one shared instance through controlled access. This can represent a process-wide school configuration, but shared state still needs careful design. A simple lazy singleton is not automatically thread-safe. An enum instance is a concise Java option; one instance does not imply immutable state.

What to remember

A singleton design provides one shared instance through controlled access. This can represent a process-wide school configuration, but shared state still needs careful design. A simple lazy singleton is not automatically thread-safe. An enum instance is a concise Java option; one instance does not imply immutable state.

Related explanations: Java Variables, Java If Statement - Syntax and Examples, Java Class - Blueprint of Objects, Exceptions in Java Introduction.

Supporting documentation: Official Java learning guide and Java 17 language specification.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Enum →