Bird
Raised Fist0

Singleton Pattern - Thread-Safe Implementation and When to Avoid It

LLDeasyInterview freq: high
Choose your preparation mode4 modes available
🏗️
Singleton Pattern - Thread-Safe Implementation and When to Avoid It
Implementing Singleton in Java with thread safety and understanding when to avoid it
easyDesign Patterns - AppliedFreq: high
Prerequisites:OOP BasicsThread Safety
⚙️
Functional Requirements
  • Provide a static method to get the singleton instance
  • Ensure thread-safe lazy initialization of the singleton instance
  • Use volatile keyword to prevent JVM instruction reordering
  • Use synchronized block or method to ensure atomic check-and-create
  • Avoid synchronization overhead on every call after initialization
  • Allow easy extension without modifying existing singleton code (Open/Closed Principle)
  • Detect and handle edge cases such as reflection or serialization breaking singleton
👤
Use Cases
  • Ensure only one instance of a class is created in a multithreaded environment
  • Provide global access point to the singleton instance
  • Prevent multiple instantiations due to race conditions
  • Avoid performance bottlenecks due to unnecessary synchronization
  • Handle lazy initialization safely
  • Detect when Singleton pattern is inappropriate (e.g., high contention or need for multiple instances)
🧑‍💻
Actors
  • ClientThread
  • SingletonInstanceManager
❓
Clarifying Questions to Ask
  • How many threads can concurrently request the singleton instance?
  • Is lazy initialization required or is eager initialization acceptable?
  • What is the expected frequency of getInstance() calls?
  • Are there any performance constraints on synchronization overhead?
  • Should the singleton support inheritance or be final?
  • How to handle edge cases like serialization or reflection?
  • Is the singleton instance expected to hold mutable state?
Out of Scope
✗ Serialization and deserialization handling for singleton
✗ Reflection attack prevention
✗ Dependency injection frameworks
✗ Multiple classloader scenarios
🔢
Enumerations
enum SingletonInitializationState
NOT_INITIALIZED | INITIALIZING | INITIALIZED
Tracks the lifecycle state of the singleton instance to prevent partially constructed instances being accessed due to JVM instruction reordering.
enum SingletonAccessMode
LAZY | EAGER
Defines the initialization strategy to clarify whether the singleton instance is created on class loading or on first access.
📦
Singleton
3 fields
private static volatile Singleton instanceprivate static final Object lock = new Object()private SingletonInitializationState initState
Manage creation and global access to the singleton instance ensuring thread safety and preventing multiple instantiations.
Why thisUse volatile and synchronized blocks in Java to ensure thread-safe lazy initialization with minimal overhead.
Why notAvoid using eager initialization or simple synchronized methods which can cause performance bottlenecks or unnecessary resource usage.
public static Singleton getInstance()Returns the singleton instance with thread-safe lazy initialization using double-checked locking and volatile to prevent JVM instruction reordering.
private Singleton()Private constructor to prevent external instantiation.
📦
ClientThread
1 fields
private String threadName
Simulates a thread requesting the singleton instance concurrently.
Why thisRepresents concurrent clients to test thread safety of singleton implementation.
Why notAvoid using single-threaded clients which do not test concurrency issues.
public void run()Calls Singleton.getInstance() to obtain the singleton instance.
📦
SingletonManager
1 fields
private Singleton singletonInstance
Manages lifecycle and access to the singleton instance, encapsulating synchronization and initialization logic.
Why thisEncapsulates singleton access logic to separate concerns and improve maintainability.
Why notAvoid placing synchronization logic directly in client code which can lead to duplication and errors.
public Singleton getSingletonInstance()Provides thread-safe access to the singleton instance, ensuring only one instance is created.
📦
SingletonTest
0 fields
Tests concurrent access to the singleton instance to verify thread safety and correctness.
Why thisAutomates concurrency testing to validate singleton implementation correctness.
Why notAvoid manual testing which is error-prone and less repeatable.
public static void main(String[] args)Creates multiple ClientThread instances to test concurrent singleton access.
📦
SingletonAvoidanceAdvisor
0 fields
Analyzes scenarios where singleton pattern should be avoided due to concurrency or design constraints.
Why thisProvides guidance on when singleton pattern is inappropriate to prevent misuse.
Why notAvoid ignoring design constraints which can lead to poor system scalability or maintainability.
public boolean shouldAvoidSingleton()Returns true if singleton pattern is not recommended based on concurrency or extensibility requirements.
🔗
Relationships
ClientThread──►Singleton[many to one]
associationClientThread does not manage Singleton lifecycle
SingletonManager◆──►Singleton[one to one]
compositionSingletonManager owns Singleton instance lifecycle
SingletonTest◇──►ClientThread[one to many]
aggregationSingletonTest creates and manages ClientThread lifecycle
SingletonAvoidanceAdvisor──►Singleton[one to one]
associationSingletonAvoidanceAdvisor queries Singleton but does not own it

Practice

(1/5)
1. In designing the Chess game, should the Piece abstraction be an abstract class or an interface, and why?
easy
A. Abstract class, because interfaces cannot enforce method implementation.
B. Interface, because pieces only declare move behavior without shared state.
C. Interface, because Java interfaces can hold state since Java 8.
D. Abstract class, because pieces share common state like position and color.

Solution

  1. Step 1: Analyze shared state among pieces

    All pieces share common state such as position on the board and color, which is best encapsulated in a base class.
  2. Step 2: Evaluate abstract class vs interface

    Abstract classes allow shared state and common method implementations, while interfaces primarily declare behavior without state.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Shared state -> abstract class preferred over interface [check]
Hint: Shared state? Use abstract class, not interface [check]
Common Mistakes:
2. In the Airline Reservation System, the SeatManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is this the correct choice?
easy
A. Factory pattern, because it controls object creation centrally for different seat types.
B. Prototype pattern, to clone seat objects efficiently for reservations.
C. Singleton pattern, to ensure only one instance manages seat allocation globally.
D. Builder pattern, to construct complex seat objects step-by-step.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single globally accessible instance.
  2. Step 2: Why Singleton fits this use case

    SeatManager must coordinate seat allocation consistently across the system, so a single instance avoids conflicting seat states.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Private constructor + static synchronized getInstance() -> Singleton [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
3. In the brokerage system, should the Order entity be designed as an abstract class or an interface? Consider that orders have common state fields like orderId and timestamp, but different types of orders have distinct execution logic.
easy
A. Abstract class, because it can hold shared state and provide default implementations for common behavior.
B. Interface, because Java 8+ interfaces can have default methods and fields for state.
C. Interface, because it allows multiple inheritance and enforces method signatures without state.
D. Abstract class, because it forces all subclasses to implement all methods explicitly.

Solution

  1. Step 1: Analyze state vs behavior requirements

    Orders share common state fields like orderId and timestamp, which require storage and possibly shared methods.
  2. Step 2: Why abstract class fits better

    Abstract classes can hold state and provide default method implementations, reducing code duplication. Interfaces cannot hold instance state.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Shared state + default behavior -> abstract class [check]
Hint: Stateful base class -> abstract class, not interface [check]
Common Mistakes:
4. In a multiplayer card game, the Hand class has a method addCard(Card card) that is not synchronized. Two threads concurrently add cards to the same hand. Which exact sequence causes a race condition leading to lost cards?
medium
A. Thread 1 reads current card list size -> context switch -> Thread 2 reads same size -> both add at same index, overwriting one card.
B. Thread 1 locks hand, adds card, unlocks; Thread 2 waits and then adds card safely.
C. Thread 1 and Thread 2 both add cards simultaneously without any interleaving.
D. Thread 1 and Thread 2 both call addCard but cards are queued and processed sequentially.

Solution

  1. Step 1: Identify the check-then-act gap

    Without synchronization, both threads read the current size of the card list before either adds their card.
  2. Step 2: Trace interleaving causing overwrite

    Thread 1 reads size N, context switches; Thread 2 reads size N; both add at index N, causing one card to overwrite the other.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized addCard -> check-then-act race -> lost card [check]
Hint: Check size -> context switch -> duplicate index add -> lost card [check]
Common Mistakes:
5. ElectricCar needs to park in ChargingSpots. What is the minimum change to the current design with VehicleType and SpotType enums, and SpotCompatibility map, to support this without modifying existing classes?
hard
A. Modify Vehicle class to include a boolean isElectric flag and add logic in ParkingLot to handle charging spots.
B. Add ELECTRIC_CAR to VehicleType, CHARGING to SpotType, create ElectricCar extending Vehicle, and update SpotCompatibility.COMPATIBILITY_MAP with new entries.
C. Create a new ElectricVehicle interface and modify all existing vehicle classes to implement it where applicable.
D. Add a ChargingSpot subclass of ParkingSpot and hardcode electric car checks in ParkingLot methods.

Solution

  1. Step 1: Identify extensibility requirements

    Adding ElectricCar and ChargingSpot requires new enum values and a new class without modifying existing classes to respect OCP.
  2. Step 2: Apply minimal change with enums and composition

    Adding new enum constants and extending Vehicle with ElectricCar allows reuse of existing logic. Updating SpotCompatibility.COMPATIBILITY_MAP with new mappings supports compatibility without changing existing code.
  3. Step 3: Eliminate options violating OCP

    Options B, C, and D modify existing classes or require widespread changes, violating OCP and risking regressions.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    New enums + subclass + compatibility map update -> minimal, OCP-compliant [check]
Hint: Add enums + subclass + map update, no existing class changes [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard