Java Notes
Complete guide to Singleton pattern — all implementation approaches, thread safety, serialization protection, enum singleton, and when to use vs avoid.
Intent
Ensure a class has only one instance and provide a global point of access to it.
UML Structure
| │ - instance | Singleton │ |
| │ - data | Object │ |
| │ + getInstance() | Singleton │ ← static factory |
| │ + getData() | Object │ |
| │ + doSomething() | void │ |
When to Use
- Database connection pools — one pool shared across the app
- Logger instances — single log manager
- Configuration managers — read once, access everywhere
- Cache managers — shared cache
- Thread pools — controlled concurrency
- Hardware interface access — single printer spooler, single GPU context
When NOT to Use
- When you need testability (hard to mock)
- When global state creates hidden dependencies
- In multi-classloader environments (each classloader gets its own instance)
- When the singleton holds mutable state shared across threads (race conditions)
Implementation 2: Lazy Initialization (NOT Thread-Safe)
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
// ❌ NOT thread-safe! Two threads can create two instances
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton(); // Race condition here
}
return instance;
}
}Pros: Lazy creation Cons: Race condition in multi-threaded environment
Implementation 3: Synchronized Method (Thread-Safe but Slow)
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
// Thread-safe but EVERY call pays synchronization cost
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}Pros: Thread-safe, lazy Cons: Synchronized keyword on every call — unnecessary overhead after first creation
Implementation 4: Double-Checked Locking (Efficient Thread-Safe)
public class DCLSingleton {
// volatile prevents instruction reordering
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) { // First check (no lock)
synchronized (DCLSingleton.class) { // Lock only first time
if (instance == null) { // Second check (inside lock)
instance = new DCLSingleton();
}
}
}
return instance; // No lock needed for subsequent calls
}
}Why volatile is Necessary
Without volatile, instruction reordering can expose a partially-constructed object:
Pros: Thread-safe, lazy, efficient (lock only on first access) Cons: Complex, verbose
Implementation 5: Bill Pugh Singleton (Static Inner Class) ⭐ Recommended
public class BillPughSingleton {
private BillPughSingleton() {}
// Inner class is not loaded until getInstance() is called
private static class SingletonHolder {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}How it works: The JVM loads SingletonHolder class only when getInstance() is first called. Class loading is inherently thread-safe. This gives you lazy initialization without explicit synchronization.
Pros: Lazy, thread-safe, no synchronization overhead, simple Cons: Cannot pass constructor arguments
Implementation 6: Enum Singleton ⭐⭐ Best Practice (Joshua Bloch)
public enum EnumSingleton {
INSTANCE;
// Fields
private Connection connection;
private Map<String, String> config = new HashMap<>();
// Constructor (called once by JVM)
EnumSingleton() {
// Initialize resources
config.put("app.name", "MyApp");
}
// Methods
public void doSomething() {
System.out.println("Working...");
}
public String getConfig(String key) {
return config.get(key);
}
}
// Usage
EnumSingleton.INSTANCE.doSomething();
String name = EnumSingleton.INSTANCE.getConfig("app.name");Working...
Pros:
- Inherently thread-safe
- Serialization-safe (JVM handles it)
- Reflection-safe (cannot create enum instances via reflection)
- Simplest correct implementation
Cons:
- Cannot extend a class (enums already extend Enum)
- Cannot be lazy (loaded when enum class is loaded)
- Looks unconventional
Breaking Singleton (and Prevention)
Attack 1: Reflection
// Breaking singleton with reflection
Constructor<EagerSingleton> constructor = EagerSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
EagerSingleton instance2 = constructor.newInstance(); // New instance!
// Prevention: Check in constructor
private EagerSingleton() {
if (INSTANCE != null) {
throw new IllegalStateException("Use getInstance()");
}
}Attack 2: Serialization
// Breaking via serialization/deserialization
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
out.writeObject(Singleton.getInstance());
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) in.readObject(); // New instance!
// Prevention: Implement readResolve()
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
protected Object readResolve() {
return getInstance(); // Return existing instance
}
}Attack 3: Cloning
// Prevention: Override clone()
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Singleton cannot be cloned");
}Real-World Example: Configuration Manager
public class ConfigurationManager {
private static volatile ConfigurationManager instance;
private final Properties properties;
private final String configFile;
private ConfigurationManager(String configFile) {
this.configFile = configFile;
this.properties = new Properties();
loadProperties();
}
public static ConfigurationManager getInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = new ConfigurationManager("application.properties");
}
}
}
return instance;
}
private void loadProperties() {
try (InputStream is = getClass().getClassLoader().getResourceAsStream(configFile)) {
if (is != null) properties.load(is);
} catch (IOException e) {
throw new RuntimeException("Failed to load config: " + configFile, e);
}
}
public String get(String key) {
return properties.getProperty(key);
}
public String get(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String val = properties.getProperty(key);
return val != null ? Integer.parseInt(val) : defaultValue;
}
public void reload() {
synchronized (properties) {
properties.clear();
loadProperties();
}
}
}Interview Questions
Q1: What is the Singleton pattern and when would you use it?
Answer: Singleton ensures only one instance exists and provides global access. Use for shared resources: connection pools, loggers, configuration, caches. Avoid when testability is critical or when the class has mutable state accessed by multiple threads.
Q2: What is the best way to implement Singleton in Java?
Answer: Enum Singleton (per Joshua Bloch) — it's inherently thread-safe, serialization-safe, and reflection-proof. For cases where you need lazy loading or constructor parameters, use the Bill Pugh (static inner class) approach.
Q3: Why is volatile needed in double-checked locking?
Answer: Without volatile, instruction reordering can let Thread B see a non-null reference to a partially constructed object (memory allocated but constructor not completed). Volatile establishes a happens-before relationship preventing this reordering.
Q4: How can you break a Singleton? How to prevent it?
Answer: Three attacks: (1) Reflection — prevent with constructor check, (2) Serialization — prevent with readResolve(), (3) Cloning — prevent by throwing in clone(). Enum Singleton is immune to all three.
Q5: Is Singleton an anti-pattern?
Answer: It can be. Problems: hidden global state, tight coupling, hard to test/mock, concurrency issues. Modern alternatives: dependency injection (Spring @Singleton scope), which provides the same "one instance" behavior but with testability and explicit dependencies.
Q6: Can a Singleton be garbage collected?
Answer: Only if its classloader is garbage collected (common in web app redeployments). In standard apps, the static reference prevents GC. In application servers, each webapp has its own classloader — redeployment can trigger Singleton recreation.
Summary
In this chapter, we learned about Singleton Design Pattern in detail. Here are the key points:
- Basic introduction to the concept and why it is needed
- Syntax and structure with complete examples
- Internal working and best practices
- Real-world applications and use cases
- Common mistakes and how to avoid them
- How this topic is asked in interviews
To practice these concepts, write and run the code in your IDE and verify the output. Modify each example and experiment so that you deeply understand the concept.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Singleton Design Pattern.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, design, patterns, singleton
Related Java Master Course Topics