Skip to content
Dev Dump

๐Ÿฆธ Superman Problem: Singleton Pattern Implementation

You are designing a library of superheroes for a video game. The library should ensure that only a single instance of any superhero (e.g., Superman) is created and shared across all consumers.

For example:

  • There should only be one Superman in the game.
  • Multiple threads or developers should not be able to create multiple instances of Superman.
  1. Single Instance: Only one instance of the class should exist
  2. Thread Safety: Must work correctly in multi-threaded environments
  3. Global Access: The instance must be accessible from anywhere in the application
  4. Lazy Initialization: Instance should be created only when needed (optional but preferred)
  5. Performance: Should not significantly impact performance

Input:

  • Multiple threads calling getInstance() method
  • Class loading and instantiation requests

Output:

  • Single instance of the class shared across all threads
  • Consistent behavior regardless of when getInstance() is called

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. The key challenge is maintaining this single instance guarantee in a multi-threaded environment where multiple threads might try to create the instance simultaneously.

  1. Make constructor private to prevent external instantiation
  2. Create a private static instance variable to hold the single instance
  3. Implement a public static getInstance() method to provide access
  4. Ensure thread safety using synchronization or volatile keywords
  5. Handle lazy initialization to create instance only when needed
  6. Consider performance implications of different synchronization approaches
  1. Race conditions when multiple threads call getInstance() simultaneously
  2. Partially constructed objects due to Java memory model optimizations
  3. Class loading issues in different JVM implementations
  4. Serialization breaking singleton guarantee
  5. Reflection bypassing private constructor
  6. Performance degradation from excessive synchronization
public class SupermanNaiveButCorrect {
// Eagerly initialize the instance
private static SupermanNaiveButCorrect superman = new SupermanNaiveButCorrect();
// Private constructor to prevent instantiation
private SupermanNaiveButCorrect() {
}
// Public method to return the single instance
public static SupermanNaiveButCorrect getInstance() {
return superman;
}
// Object method
public void fly() {
System.out.println("I am Superman & I can fly!");
}
}
public class SupermanWithFlaws {
private static SupermanWithFlaws superman;
private SupermanWithFlaws() {
}
public static SupermanWithFlaws getInstance() {
if (superman == null) {
superman = new SupermanWithFlaws();
}
return superman;
}
public void fly() {
System.out.println("I am Superman & I can fly!");
}
}
public class SupermanCorrectButSlow {
private static SupermanCorrectButSlow superman;
private SupermanCorrectButSlow() {
}
public static synchronized SupermanCorrectButSlow getInstance() {
if (superman == null) {
superman = new SupermanCorrectButSlow();
}
return superman;
}
public void fly() {
System.out.println("I am Superman & I can fly!");
}
}
public class SupermanSlightlyBetter {
private static SupermanSlightlyBetter superman;
private SupermanSlightlyBetter() {
}
public static SupermanSlightlyBetter getInstance() {
if (superman == null) { // First check (without synchronization)
synchronized (SupermanSlightlyBetter.class) {
if (superman == null) { // Second check (with synchronization)
superman = new SupermanSlightlyBetter();
}
}
}
return superman;
}
public void fly() {
System.out.println("I am Superman & I can fly!");
}
}
public class Superman {
// Volatile ensures visibility and prevents reordering
private static volatile Superman superman;
private Superman() {
}
public static Superman getInstance() {
if (superman == null) { // First check (without synchronization)
synchronized (Superman.class) {
if (superman == null) { // Second check (with synchronization)
superman = new Superman();
}
}
}
return superman;
}
public void fly() {
System.out.println("I am Superman & I can fly!");
}
}

Scenario: Two threads calling getInstance() simultaneously

Thread 1 and Thread 2 both call getInstance() at the same time:

  1. Initial State: superman = null

  2. Thread 1:

    • Checks if (superman == null) โ†’ true
    • Enters synchronized block
    • Checks again if (superman == null) โ†’ true
    • Creates new instance: superman = new Superman()
    • Returns instance
  3. Thread 2:

    • Checks if (superman == null) โ†’ false (instance already created)
    • Returns existing instance without entering synchronized block

Result: Only one instance is created, both threads get the same instance.

Time Complexity:

  • Eager Initialization: O(1) - instance created at class loading
  • Lazy Initialization: O(1) after first creation, O(1) for subsequent calls
  • Synchronized: O(1) but with synchronization overhead
  • Double-Checked Locking: O(1) with minimal synchronization overhead

Space Complexity:

  • All implementations: O(1) - only one instance stored

ChallengeDescription
๐Ÿ” Race ConditionsMultiple threads creating instances simultaneously
๐Ÿงต Thread SafetyEnsuring single instance in multi-threaded environment
โšก PerformanceMinimizing synchronization overhead
๐Ÿ—๏ธ Memory ModelHandling Javaโ€™s memory model complexities

Use the Singleton Pattern with appropriate synchronization to ensure:

  1. Only one instance of the class is created during the applicationโ€™s lifetime.
  2. The same instance is returned to all requesting consumers.
  3. Thread safety is maintained without significant performance impact.

  • ๐Ÿšซ Private constructor prevents external instantiation
  • ๐Ÿ”’ Synchronization ensures thread safety during creation
  • ๐Ÿ”„ Double-checking minimizes synchronization overhead
  • ๐Ÿ“ Static instance provides global access point

Think of a government office that can only have one director. No matter how many people ask for the director, they always get the same person. The office ensures only one director exists and provides a consistent way to access them.


  • Always mention thread safety considerations
  • Discuss the trade-offs between different implementations
  • Explain why volatile is needed in double-checked locking
  • Consider alternative patterns like enum singletons
  • Mention serialization and reflection challenges

โœ… FeatureCovered
Thread-safeโœ… Yes
Single instanceโœ… Yes
Performanceโœ… Yes
Lazy loadingโœ… Yes
ImplementationThread-SafeLazy InitializationPerformanceComplexity
Naive SingletonNoNoHigh (Eager Loading)Simple
Lazy Initialization (Flawed)NoYesHighSimple
Thread-Safe SingletonYesYesLow (Synchronized)Moderate
Double-Checked Locking (DCL)YesYesHighComplex
Correct DCL (With volatile)YesYesHighModerate