How to protect Singleton design pattern from multiple instantiations?
How to protect Singleton design pattern from multiple instantiations?
Singleton design pattern is one of the popular design pattern, which ensures that only one instance of a class can be created and provides a global point of access to that instance. But there are many ways through which hackers can break this.
Following are the ways by which multiple instances can be created for Singleton class and preventive measures to protect the code:
1.    Reflection
Reflection can be caused to destroy singleton property of singleton class, as shown in following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
class Singleton { // static initializer for Singleton Class instance   private static Singleton instance = new Singleton();   private Singleton() {}   public static Singleton getInstance()   {     return instance;   } } public class ReflectionTest {   public static void main(String[] args)   {     Singleton instance1 = Singleton.instance;     Singleton instance2 = null;     try     {       Constructor[] constructors =           Singleton.class.getDeclaredConstructors();       for (Constructor constructor : constructors)       {         // Below code will destroy the singleton pattern         constructor.setAccessible(true);         instance2 = (Singleton) constructor.newInstance();         break;       }     }     catch (Exception e)     {       e.printStackTrace();     }   System.out.println("instance1 hashcode- "                    + instance1.hashCode());   System.out.println("instance2 hashcode- "                    + instance2.hashCode());   } } |
The above code ends up creating two instances of the Singleton class which breaks the rule.
Output
1 2 |
instance1 hashcode- 865113938 instance2 hashCode 1442407170 |
Solution
To overcome issue raised by reflection, enums are used because java ensures internally that enum value is instantiated only once. Since java Enums are globally accessible, they can be used for singletons. Its only drawback is that it is not flexible i.e it does not allow lazy initialization.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public enum Singleton { INSTANCE } public class ReflectionTest { Â Â public static void main(String[] args) Â Â { Â Â Â Â Singleton instance1 = Singleton.INSTANCE; Â Â Â Â Singleton instance2 = Singleton.INSTANCE; System.out.println("instance1 hashcode- " Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â + instance1.hashCode()); Â Â System.out.println("instance2 hashcode- " Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â + instance2.hashCode()); Â Â } } |
JVM handles the creation and invocation of enum constructors internally. As enums don’t give their constructor definition to the program, it is not possible for us to access them by Reflection also.
2.    Cloning
Cloning is the concept to create copy of object, it comes under prototype design pattern and it can break Singleton pattern by creating of copy of singleton instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class SuperClass implements Cloneable{ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Singleton extends SuperClass { // static initializer for Singleton Class instance private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } public static void main(String[] args) throws CloneNotSupportedException { Singleton instance1 = Singleton.instance; Singleton instance2 = (Singleton) instance1.clone(); System.out.println("instance1 hashcode- "+instance1.hashCode()); System.out.println("instance2 hashCode "+ instance2.hashCode());; } |
Output
1 2 |
instance1 hashcode- 865113938 instance2 hashCode 1442407170 |
Solution
Override clone() method and throw an exception from clone method that is CloneNotSupportedException
. Now whenever user will try to create clone of singleton object, it will throw exception and hence our class remains singleton.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Singleton extends SuperClass { // static initializer for Singleton Class instance private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } @Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } } |
Output
1 2 3 |
Exception in thread "main" java.lang.CloneNotSupportedException at DesignPattern.SingletonCloningTest$Singleton.clone(SingletonCloningTest.java:29) at DesignPattern.SingletonCloningTest.main(SingletonCloningTest.java:39) |
3.    Serialization
Serialization is used to convert an object of byte stream and save in a file or send over a network. Suppose you serialize an object of a singleton class. Then when de-serializing that object it will create a new instance and break the singleton pattern.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Singleton implements Serializable { // static initializer for Singleton Class instance private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException { Singleton instance1 = Singleton.instance; ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file.text")); out.writeObject(instance1); out.close(); // Deserailize to object ObjectInput in = new ObjectInputStream(new FileInputStream("file.text")); Singleton instance2 = (Singleton) in.readObject(); in.close(); System.out.println("instance1 hashcode- "+instance1.hashCode()); System.out.println("instance2 hashCode "+ instance2.hashCode());; } |
Output
1 2 |
instance1 hashcode- 865113938 instance2 hashCode 1442407170 |
Solution
Implement method readResolve() method of Serializable interface and return instance of Singleton class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Singleton implements Serializable { /** * */ private static final long serialVersionUID = 1L; // static initializer for Singleton Class instance private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } // implement readResolve method @Override protected Object readResolve() { return instance; } } |
Output
1 2 |
instance1 hashcode- 865113938 instance2 hashCode 865113938 |
These were the ways to protect Singleton class from multiple instantiations.
Happy Learning!!