Is Your Java Singleton Really a Singleton? Thread, Reflection & Serialization Pitfalls
This article examines various Java singleton implementations—eager, lazy, double‑checked locking, static inner class, enum, and Kotlin object—analyzing their thread safety, reflection and serialization vulnerabilities, and shows how to harden them using volatile, constructor guards, and readResolve.
1. General Singleton Implementations
1.1 Eager Singleton
public class HungrySingleton {
private static final HungrySingleton mInstance = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() { return mInstance; }
}The private constructor prevents external instantiation. The eager version creates the instance at class‑loading time, which is thread‑safe, but it may waste resources if the instance is heavy.
1.2 Lazy Singleton
public class LazySingleton {
private static LazySingleton mInstance;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (mInstance == null) {
mInstance = new LazySingleton();
}
return mInstance;
}
}Instance creation is delayed until getInstance() is called, but the synchronized method incurs a performance cost.
1.3 Double‑Checked Locking (DCL)
public class DCLSingleton {
private static DCLSingleton mInstance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (mInstance == null) {
synchronized (DCLSingleton.class) {
if (mInstance == null) {
mInstance = new DCLSingleton();
}
}
}
return mInstance;
}
}DCL reduces synchronization overhead but suffers from possible instruction reordering, which can expose a partially constructed object to other threads.
Reordering steps in JVM: Allocate memory for the object. Assign the reference to mInstance . Initialize the object.
When reordering occurs, Thread A may assign the reference before the object is fully initialized, allowing Thread B to see a non‑initialized instance.
1.4 Fixing DCL with volatile
Marking mInstance as volatile prevents the reordering that breaks DCL, making it truly thread‑safe in JDK 1.5 and later.
1.5 Static Inner Class Singleton
public class StaticInnerSingleton {
private StaticInnerSingleton() {}
private static class SingletonHolder {
private static final StaticInnerSingleton mInstance = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance() { return SingletonHolder.mInstance; }
}This approach combines lazy loading with class‑loader‑based thread safety, avoiding explicit synchronization.
2. Is It Really a Singleton?
Beyond thread safety, a robust singleton must also resist reflection and serialization attacks.
2.1 Reflection Safety
public static void main(String[] args) throws Exception {
DCLSingleton s1 = DCLSingleton.getInstance();
Constructor<DCLSingleton> ctor = DCLSingleton.class.getDeclaredConstructor();
ctor.setAccessible(true);
DCLSingleton s2 = ctor.newInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}The output shows two different hash codes, proving that reflection can break the singleton. To prevent this, the constructor can be guarded:
private DCLSingleton() {
if (mInstance != null) {
throw new RuntimeException("Reflection is not allowed!");
}
}2.2 Serialization Safety
public static void main(String[] args) throws Exception {
DCLSingleton s1 = DCLSingleton.getInstance();
// Serialize
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
out.writeObject(s1);
out.close();
// Deserialize
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
DCLSingleton s2 = (DCLSingleton) in.readObject();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}Deserialization creates a new instance. Overriding readResolve() fixes this:
protected Object readResolve() { return getInstance(); }2.3 Enum Singleton
public enum EnumSingleton { INSTANCE; }Enum singletons are inherently thread‑safe, serialization‑safe, and resistant to reflection because the JVM forbids reflective creation of enum objects.
2.4 Kotlin Object Singleton
object KotlinSingleton { }Kotlin’s object is compiled to a class with a static final instance, effectively an eager singleton.
3. Takeaways
While many singleton implementations exist, the enum (or Kotlin object) provides the simplest, most robust solution. For legacy code, using volatile with DCL, guarding constructors, and implementing readResolve() are essential to achieve a truly single instance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
