Three ways to destroy single case mode

Definition of singleton mode:

Singleton mode is a common software design mode. In its core structure, there is only one special class called singleton. Through the singleton pattern, we can ensure that there is only one instance of a class in the system. That is, a class has only one object instance.

In java, there are three kinds of singleton patterns that we often use (I don't know which killer says there are seven, I'm too lazy to find )
In fact, in our daily application, we will encounter some problems: how is the singleton mode broken? Is the single mode invincible? Since the singleton mode will be destroyed, is there any way to write a singleton mode that cannot be destroyed?
This time, we will discuss the destruction of single mode and the benign and malignant destruction of single mode. First of all, we understand three modes of a single case mode:

Slacker:
The most commonly used form. For specific codes, please refer to this:

public class IdlerSingleton {

    private static IdlerSingleton idlerSingleton;

    /**
     * 
     */
    private IdlerSingleton() {
        // constructed by Velociraptors
    }

    public static synchronized IdlerSingleton getInstance() {
        if (idlerSingleton == null) {
            idlerSingleton = new IdlerSingleton();
        }
        return idlerSingleton;
    }

}

Hungry Chinese:

public class HungrySingleton {

    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    /**
     * 
     */
    private HungrySingleton() {
        // constructed by Velociraptors
    }

    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }

}

Double check lock type:

public class DoubleLockSingleton {

    private static volatile DoubleLockSingleton doubleLockSingleton;

    /**
     * 
     */
    private DoubleLockSingleton() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingleton getInstance() {
        if (doubleLockSingleton == null) {
            synchronized(DoubleLockSingleton.class) {
                if(doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingleton();
                }
            }
        }
        return doubleLockSingleton;
    }

}

At first glance, this double lock is no different from the lazy mode, but in fact, the execution efficiency of the double lock is higher, and the concurrent effect is better.

In this mode, the synchronization content is placed inside the if to improve the efficiency of execution. It is not necessary to synchronize every time the object is acquired, but only the first time. After creation, it is unnecessary.
The method of double judgment and synchronization in double lock mode is much more efficient than that in the first example, because if the single-layer if judgment is allowed by the server, if there are 100 threads, the time consumed is 100 * (synchronous judgment time + if judgment time). If the double if judgment is allowed, 100 threads can be judged at the same time if, and the time consumed in theory is only one if judgment Break time. So if we face the situation of high concurrency and adopt the lazy mode, the best choice is the way of double judgment and synchronization.

The implementation effect of the above three methods is to make the object have only one instance. However, in the actual business requirements, not everyone plays the game according to the rules you have made (even the chicken eating is hung by someone )
So what can destroy the singleton mode?

1. Clone
Clone destroy single instance mode is not a kind of damage strictly. For example (Note: it's not human) single instance mode is to lock the class, and then use the same instance every time when using this class, but it will lose our control over this class. We may occasionally have several days in the future, and we need to have more than one such instance So if you lock this class, it doesn't make sense. Therefore, we need to lock at ordinary times, but we can pry it open at the critical moment. Cloning is such a benign single instance mode destruction method. The specific methods are as follows:

public class DoubleLockSingleton implements Cloneable {

    private static volatile DoubleLockSingleton doubleLockSingleton;

    /**
     * 
     */
    public DoubleLockSingleton() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingleton getInstance() {
        if (doubleLockSingleton == null) {
            synchronized (DoubleLockSingleton.class) {
                if (doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingleton();
                }
            }
        }
        return doubleLockSingleton;
    }

    // Override by Velociraptors
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // auto created by Velociraptors
        return super.clone();
    }

    public void method() {
        System.out.println("Hello DoubleLockSingleton!");
    }

}

Test:

public class SingletonLauncher {

    /**
     * @param args
     * @throws CloneNotSupportedException 
     */
    public static void main(String[] args) throws CloneNotSupportedException {
        // auto created by Velociraptors
        DoubleLockSingleton doubleLockSingleton = DoubleLockSingleton.getInstance();
        DoubleLockSingleton doubleLockSingleton2 = (DoubleLockSingleton)doubleLockSingleton.clone();
        if(doubleLockSingleton == doubleLockSingleton2) {
            System.out.println("Singleton break failed");
        } else {
            doubleLockSingleton.method();
            doubleLockSingleton2.method();
        }
    }

}
//Hello DoubleLockSingleton!
//Hello DoubleLockSingleton!

The results are as follows:

Hello DoubleLockSingleton! 
Hello DoubleLockSingleton!

As the code shows, to destroy the singleton mode by cloning, you need to inherit the Colneable interface and override the clone method. When your code writes like this, it means that you are prepared to destroy the singleton mode in the future.

2. Reflection
As we all know, through java's reflection mechanism, it is not only easy to create an instance, but also easy to map the structure of the entire java class itself.
Test:

public class SingletonLauncher {

    /**
     * @param args
     * @throws CloneNotSupportedException
     */
    public static void main(String[] args) throws Throwable {
        // auto created by Velociraptors
        IdlerSingleton idlerSingleton = IdlerSingleton.getInstance();
        Class<?> clazz = Class.forName("com.singleton.d1213.IdlerSingleton");
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        IdlerSingleton idlerSingleton2 = (IdlerSingleton) constructor.newInstance();
        if(idlerSingleton == idlerSingleton2) {
            System.out.println("Singleton break failed!");
        }else {
            System.out.println("Singleton break succeed!");
        }
    }

}
// Singleton break succeed!

The results are as follows:

Singleton break succeed!

The method of setAccessible(true) is to skip the detection syntax in java, which is unreasonable. It is a malicious single instance destruction. At this time, our approach is as follows:

public class IdlerSingleton {

    private static IdlerSingleton idlerSingleton;

    /**
     * 
     */
    private IdlerSingleton() {
        // constructed by Velociraptors
        synchronized (IdlerSingleton.class) {
            if (ConstantClass.idlerSingletonCount > 0) {
                // Throw an exception to abort the constructor call and suppress the destructive action
                throw new RuntimeException("This singleton pattern class can not create more object!");
            }
            ConstantClass.idlerSingletonCount++;
        }
    }

    public static synchronized IdlerSingleton getInstance() {
        if (idlerSingleton == null) {
            idlerSingleton = new IdlerSingleton();
        }
        return idlerSingleton;
    }

    /**
     * 
     * @By Velociraptors
     * @Creation time: 4:35:35 PM
     * @Version date: December 13, 2017
     *
     */
    static class ConstantClass {

        public static int idlerSingletonCount = 0;

        /**
         * 
         */
        private ConstantClass() {
            // constructed by Velociraptors
        }

    }

}

Test results:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at com.singleton.d1213.SingletonLauncher.main(SingletonLauncher.java:28)
Caused by: java.lang.RuntimeException: This singleton pattern class can not create more object!
    at com.singleton.d1213.IdlerSingleton.(IdlerSingleton.java:26)
    ... 5 more

Success prevented a wave of destruction.

3. Serializable
Serialization is also a powerful tool for breaking singleton patterns. It is similar to clone in nature. It needs class to implement serialization interface. Compared with clone, it is more inevitable to implement serialization in actual operation. For some classes, it is necessary to serialize.
For example, there is a serialized class and implements the double lock singleton mode:

public class DoubleLockSingletonSerializable implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 972132622841L;
    private static volatile DoubleLockSingletonSerializable doubleLockSingleton;

    /**
     * 
     */
    public DoubleLockSingletonSerializable() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingletonSerializable getInstance() {
        if (doubleLockSingleton == null) {
            synchronized (DoubleLockSingletonSerializable.class) {
                if (doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingletonSerializable();
                }
            }
        }
        return doubleLockSingleton;
    }

    public void method() {
        System.out.println("Hello DoubleLockSingleton!");
    }

}

Test:

public class SingletonLauncher {

    /**
     * @param args
     * @throws CloneNotSupportedException
     */
    @SuppressWarnings("resource")
    public static void main(String[] args) throws Throwable {
        // auto created by Velociraptors
        DoubleLockSingletonSerializable doubleLockSingletonSerializable = DoubleLockSingletonSerializable.getInstance();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("tempFile"));
        objectOutputStream.writeObject(doubleLockSingletonSerializable);
        File file = new File("tempFile");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        DoubleLockSingletonSerializable doubleLockSingletonSerializable2 = (DoubleLockSingletonSerializable) objectInputStream.readObject();
        if (doubleLockSingletonSerializable == doubleLockSingletonSerializable2) {
            System.out.println("Singleton break failed!");
        } else {
            System.out.println("Singleton break succeed!");
        }
    }

}

The results are as follows:

Singleton break succeed!

The object obtained by serializing and deserializing singleton is a new object, which destroys Singleton's Singleton's singleton.
Looking at the source code of the ObjectInputStream, you will find a problem: one of the methods is called readOrdinaryObject

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }
        //start
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        //end

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);
        //start
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }
        //end

        return obj;
    }

In the first part, start end, we can see clearly that the bottom layer of serialization still uses reflection to destroy the single instance. Then look at the second part of start end carefully. We can see that when the bottom layer of serialization adopts reflection, it will check whether the class that implements the serialization interface contains readResolve. If it does, it will return true, and then it will call the readResolve method. It's easy to do. To prevent serialization from breaking the singleton pattern, you only need to declare a readResolve method.

public class DoubleLockSingletonSerializable implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 972132622841L;
    private static volatile DoubleLockSingletonSerializable doubleLockSingleton;

    /**
     * 
     */
    public DoubleLockSingletonSerializable() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingletonSerializable getInstance() {
        if (doubleLockSingleton == null) {
            synchronized (DoubleLockSingletonSerializable.class) {
                if (doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingletonSerializable();
                }
            }
        }
        return doubleLockSingleton;
    }

    public void method() {
        System.out.println("Hello DoubleLockSingleton!");
    }

    private Object readResolve() {
        return doubleLockSingleton;
    }

}

The re test results are as follows:

Singleton break failed!

So far, some of the content I have learned has been expressed here. Even in simple single case mode, if we want to dig deep into the principle, there are still some holes for us to fill.

50 original articles published, 34 praised, 210000 visitors+
Private letter follow

Tags: Java

Posted on Mon, 16 Mar 2020 06:32:32 -0400 by pendelton