Difficulty: Intermediate
Implement a thread-safe Singleton in Java. What are the ways to break it and how do you prevent that?
The Singleton pattern ensures that a class has exactly one instance and provides a global point of access to it. Typical uses are a configuration holder, a logger, a connection pool, or a cache manager. It is the most asked design pattern in fresher interviews, mostly because it is deceptively easy to write wrongly.
The basic idea has three parts: a private constructor so nobody else can call new, a private static field holding the single instance, and a public static method, usually getInstance(), that returns it.
Now the implementations, from naive to best. Eager initialisation creates the instance when the class loads: private static final Config INSTANCE = new Config(). It is thread-safe by virtue of class loading, simple, but creates the object even if it is never used. Lazy initialisation without synchronisation checks if the instance is null and creates it, but two threads can both see null and create two instances, so it is broken in multi-threaded code. Making getInstance synchronised fixes it but locks on every call, which is slow.
The common answer in interviews is double-checked locking. Check for null without a lock, and only if it is null enter a synchronised block and check again before creating. The critical detail is that the field must be volatile. Without volatile, the JVM may reorder the steps of object creation (allocate, assign reference, run constructor), so another thread might see a non-null reference to a half-constructed object. Forgetting volatile is the classic mistake interviewers look for.
Two cleaner approaches exist. The initialisation-on-demand holder idiom uses a private static nested class that holds the instance; the JVM loads the nested class lazily on first access, and class loading is thread-safe, giving lazy and lock-free behaviour. The best option by Effective Java is a single-element enum: enum AppSettings { INSTANCE; }. The JVM guarantees one instance, it is thread-safe, and it is immune to reflection and serialisation attacks.
How can a Singleton be broken? Reflection: calling setAccessible(true) on the private constructor creates another instance. Serialisation: deserialising creates a new object unless you implement readResolve to return the existing instance. Cloning: if the class implements Cloneable, clone() can produce a copy, so override it to throw. Multiple class loaders: each loader may load its own copy of the class, yielding one instance per loader. Enum singletons resist reflection and serialisation.
Also be ready with the criticisms. Singletons are global state in disguise, which hides dependencies, makes unit testing difficult since you cannot easily replace them with a mock, and can create tight coupling. In modern code, a better approach is to let a dependency injection container manage a single instance (Spring beans are singleton-scoped by default) while classes receive it through constructors. That gives you the single instance without the global access.
class Config {
private static volatile Config instance;
private Config() { System.out.println("Config created"); }
static Config getInstance() {
if (instance == null) { // first check, no lock
synchronized (Config.class) {
if (instance == null) { // second check, with lock
instance = new Config();
}
}
}
return instance;
}
}
public class SingletonDemo {
public static void main(String[] args) {
Config a = Config.getInstance();
Config b = Config.getInstance();
System.out.println(a == b);
}
}
enum AppSettings {
INSTANCE;
private String theme = "dark";
String getTheme() { return theme; }
void setTheme(String t) { theme = t; }
}
public class EnumSingletonDemo {
public static void main(String[] args) {
AppSettings.INSTANCE.setTheme("light");
System.out.println(AppSettings.INSTANCE.getTheme());
}
}
singleton, design-patterns, thread-safety, double-checked-locking, enum