Anti Patterns - Singleton (in Java)
Problem 01: Thread-Safety Problem
Lazy initialization is one of the good implementations of singleton design patterns. It is simple to implement and memory efficient - right?
If you say - Yes!
Let me tell you dear, not all the time! Let’s check the sweet simple lazy initialization singleton design pattern implementation:
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Let’s move our attention from single-threaded applications to multi-threaded applications:
class Main {
public static void main(String[] args) {
Set<Singleton> set = new HashSet<>();
for (int i = 0; i < 1000; i++) {
new Thread(() -> set.add(Singleton.getInstance())).start();
}
System.out.println(set);
}
}
If we run the above code several times, you will notice a similar kind of output mentioned below sometimes:
[Singleton@d9f80ba, Singleton@624997e3]
A clear indication, the set
contains multiple Singleton
objects in a multithreaded environment.
Solution: Bill Pugh Singleton Design
Bill Pugh Singleton implementation is one of the best easy solutions for the thread safety singleton design pattern problem. Here is the implementation:
public class Singleton {
private Singleton() {
}
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
Here, the INSTANCE
will not be instantiated as long as the getInstance()
method is not being called for the first time, as the Java class loader will not load the SingletonHelper
class before get a getInstance()
method call.
Problem 02: Double-Checked Locking Without volatile
If we don’t declare instance
as volatile
during double-checked locking implementation, there could be memory visibility issue as well as instruction reordering problem.
Solution: Always Declare instance
Object as volatile
public class Singleton {
private static volatile Singleton instance; // use volatile to ensure visibility
// and solve instruction re-ordering problem
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) { // first check
synchronized (Singleton.class) {
if (instance == null) { // second check
instance = new Singleton();
}
}
}
return instance;
}
}
Problem 03: Deserialization Problem
Let’s take the great Bill Pugh singleton pattern implementation with enabling serialization.
public class Singleton implements Serializable {
private Singleton() {
}
private static class SingletonHelper {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.instance;
}
}
Now let’s serialize the Singleton instance:
class SerializationDemo {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
try (FileOutputStream fileOut = new FileOutputStream("singleton.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(singleton);
System.out.println("Serialized data is saved in singleton.ser");
} catch (IOException i) {
System.err.println(i.getMessage());
}
}
}
Now the tricky part. Let’s deserialize it and create two singleton instances - singleton1
, and singleton2
.
class DeserializationDemo {
public static void main(String[] args) {
Singleton singleton1 = deserialize();
Singleton singleton2 = deserialize();
System.out.println(singleton1 == singleton2);
System.out.println(singleton1);
System.out.println(singleton2);
}
private static Singleton deserialize() {
Singleton singleton = null;
try (FileInputStream fileIn = new FileInputStream("singleton.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
singleton = (Singleton) in.readObject();
} catch (Exception e) {
System.err.println(e.getMessage());
}
return singleton;
}
}
You will see a similar kind of output mentioned below:
false
Singleton@1b4fb997
Singleton@deb6432
From the output, it is clear that the deserialization of a singleton instance could easily violate the basic requirements of a singleton class.
Solution: readResolve()
is the rescue
Let me quote from the Java Object Serialization Specification:
For Serializable and Externalizable classes, the
readResolve
method allows a class to replace/resolve the object read from the stream before it is returned to the caller.
So add the following method to your Singleton
class & boom!
@Serial
protected Object readResolve() {
return getInstance();
}
Run the serialization and deserialization again, now you will get a similar kind of output:
true
Singleton@28ba21f3
Singleton@28ba21f3
Problem 03: Cloning Problem
Similar to the Deserialization Problem, a cloneable singleton class could create multiple instances.
public class Singleton implements Cloneable {
private Singleton() {
}
private static class SingletonHelper {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.instance;
}
@Override
protected Singleton clone() throws CloneNotSupportedException {
return (Singleton) super.clone();
}
}
class CloningDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = singleton1.clone();
System.out.println(singleton1 == singleton2);
System.out.println(singleton1);
System.out.println(singleton2);
}
}
Output:
false
Singleton@3feba861
Singleton@5b480cf9
Clear indication, cloning creates multiple instances of the Singleton
class.
Prevention: Don’t Allow Cloning
So change the clone()
method implementation to prevent the creation of multiple instances of the Singleton
class.
@Override
protected Singleton clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Cloning not supported");
}
Problem 04: Hidden Dependencies
Singleton instances could create hidden dependencies in our code. It becomes hard to test the program and also reduces the reusability of our implementation.
public class HiddenDependency {
public void doSomething() {
Singleton singleton = Singleton.getInstance();
// other implementation
}
}
Solution: Use Dependency Injection
Dependency injection will make dependencies explicit, which will make our code testable and reusable.
public class ExplicitDependency {
private final Singleton singleton;
public ExplicitDependency(Singleton singleton) {
this.singleton = singleton;
}
public void doSomething() {
// use singleton
// other implementation
}
}
Subscribe to my newsletter
Read articles from Shikhor Roy directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by