Anti Patterns - Singleton (in Java)

Shikhor RoyShikhor Roy
5 min read

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
    }
}
0
Subscribe to my newsletter

Read articles from Shikhor Roy directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shikhor Roy
Shikhor Roy