Single case analysis

Keywords: Programming Java

Singleton mode, which should be understood by all java developers, is not necessary for those defective singleton modes. Too low. Here are two kinds of singleton modes.

1, Double check lock

First code:

package com.ayo.singleton;

import java.io.*;

/**
 * Double check lock single instance mode
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {}

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

The double check lock is basically a relatively perfect single instance mode (in most scenarios), but it still has disadvantages. If you write it like this, you can't really guarantee a single instance. The simplest way is to use reflection to trick you:

package com.ayo.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * Double check lock single instance mode
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {}

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

    public static void main(String[] args) throws Exception {

        /**
         * Reflection get object
         */
        LanhanSingleton instance = LanhanSingleton.getInstance();
        //Get constructor of class
        Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor();
        lanhanSingletonConstructor.setAccessible(true);
        LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance();
        System.out.println("Is the object obtained by reflection the same as the singleton object? = " + (instance == lanhanSingleton1));
    }

}

This is a good prevention, because the class is defined by yourself

package com.ayo.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * Double check lock single instance mode
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {
        //Stopping reflex
        if (INSTANCE != null && this != INSTANCE){
            throw new SecurityException("Reflect on your sister! ");
        }
    }

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

    public static void main(String[] args) throws Exception {
        LanhanSingleton instance = LanhanSingleton.getInstance();
        /**
         * Reflection get object
         */
        //Get constructor of class
        Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor();
        lanhanSingletonConstructor.setAccessible(true);
        LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance();
        System.out.println("Is the object obtained by reflection the same as the singleton object? = " + (instance == lanhanSingleton1));
    }

}

Reflection is easy to disable, but there is another method, deserialization, which can also generate a new object. Take a look

package com.ayo.singleton;

import java.io.*;

/**
 * Double check lock single instance mode
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {
        //Stopping reflex
        if (INSTANCE != null && this != INSTANCE){
            throw new SecurityException("Reflect on your sister! ");
        }
    }

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

    public static void main(String[] args) throws Exception {
        /**
         * serialize
         */
        //Get output stream
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("lanhanFile"));
        //Serialize singleton objects to disk
        oos.writeObject(LanhanSingleton.getInstance());
        //Get input stream
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("lanhanFile"));
        //Read serialized singleton object from disk
        LanhanSingleton lanhanSingleton = (LanhanSingleton) ois.readObject();
        //Judge whether the two are equal
        System.out.println("Is the deserialized object equal to the original? = " + (lanhanSingleton == LanhanSingleton.getInstance()));
    }

}

I don't think it's silly. Why does the object change after deserialization? Let's go in and have a look (mark your breakpoint debug)

When following the source code, you can see the following methods:

ok, when you follow here, you find that reflection is called (using the constructor in ObjectStreamClass, so is it useless for you to add verification to the constructor? No, this kind of constructor can't be adjusted by the programmer, but you can't forbid it, because it will be adjusted during deserialization, and there will be problems in the same way. What should I do , I know you're confused again. Don't worry. Keep reading the source code

Carefully debug this logic and set breakpoints. At last, you will find that it has entered the hasReadResolveMethod method,

As a result, the expression desc.hasReadResolveMethod() is false. After further analysis, it is found that this readResolveMethod is actually a reflection method to obtain the readResolve method in desc (COM. Ayo. Singleton. Lanhansingleton: static final long serialVersionUID = - 65557666772060599999l;). If this method is not empty, the returned object is actually the return value of this method. Add a method

private Object readResolve(){
        return INSTANCE;
    }

It's settled.

Two. Enumeration

Directly on the code (tired, see the source code really see eye ache)

package com.ayo.singleton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

/**
 * Enumeration based singleton mode
 *
 * @Authror ayo
 * @Date 2020/1/7 14:53
 */
public enum EnumSingleton {

    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) throws Exception {
        /**
         * serialize
         */
        //Get output stream
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enumFile"));
        //Serialize singleton objects to disk
        oos.writeObject(INSTANCE);
        //Get input stream
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumFile"));
        //Read serialized singleton object from disk
        EnumSingleton enumSingleton = (EnumSingleton) ois.readObject();
        //Judge whether the two are equal
        System.out.println("Is the deserialized object equal to the original? = " + (INSTANCE == enumSingleton));

        Thread.sleep(1000);

        /**
         * Reflection get object
         */
        //Get constructor of class
        Constructor<EnumSingleton> enumSingletonConstructor = EnumSingleton.class.getDeclaredConstructor();
        enumSingletonConstructor.setAccessible(true);
        EnumSingleton enumSingletonReflect = enumSingletonConstructor.newInstance();
        System.out.println("Is the object obtained by reflection the same as the singleton object? = " + (INSTANCE == enumSingletonReflect));
    }
}

At this time, you will find the advantages of enumerating singletons. Both problems are avoided. Why?

First, deserialization,

The previous steps are the same as ordinary objects, but in this step, you will find that the enumeration is TC? Enum. Analyze the readEnum method, and see the valueof method

And then there's reflection,

This is simpler. In fact, it is not allowed to reflect enumeration. Through the source code analysis, we can know that enumeration classes are specially treated, so the natural support for singleton mode.

Posted by adams0423 on Tue, 07 Jan 2020 03:52:47 -0800