Java interview questions, can the singleton pattern you wrote prevent deserialization and reflection?

preface

Speaking of singleton mode, I believe everyone will not be unfamiliar. Because compared with other design patterns, it is relatively simple to implement a singleton pattern. Singleton pattern means that a class has only one instance.

To obtain instances of a class, we often use the new keyword, but we should ensure that a class can only have one instance, so developers using this class cannot use the new keyword to create instances. That is, the constructor of the class cannot be called externally, so it is easy to think that the constructor of the class is private, so developers cannot create the object of the class outside the class through the new method.

Since the external cannot create the object of the singleton class through the new keyword, the singleton class itself must provide a static method, so that the external can obtain the object of the singleton class through the method of class name + method name.

These are the two characteristics of singleton mode:

  • Construction method private
  • A static method is provided to enable the external to obtain the instance of the singleton class through this method

Almost all singleton pattern implementations focus on these two points

Singleton mode

Singleton mode, also known as singleton mode, is a commonly used software design mode, which belongs to the creation mode. When applying this pattern, the class of a singleton object must ensure that only one instance exists.

According to the timing of instance creation, the singleton mode can be roughly divided into two types: hungry singleton and lazy singleton.

  • Hungry Chinese singleton refers to initializing an object when the singleton class is loaded, regardless of whether it will be used by later programs. Because I can't wait and feel very hungry, it's called hungry man style single case.
  • Lazy singleton is similar to lazy loading. It is instantiated only when the program is used for the first time, so it is called lazy singleton.

Hungry Han style single case

Because the hungry Chinese single example is relatively simple, the source code is given directly

/**
 * Evil Chinese style, singleton, thread safety
 * @author sicimike
 * @create 2020-02-23 20:15
 */
public class Singleton1 {

    private static final Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {}

    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

In addition, there is another variant of hungry Chinese singleton, which is to put the instantiation process in a static code block

/**
 * Hungry Han style, singleton, thread safety
 * @author sicimike
 * @create 2020-02-23 20:19
 */
public class Singleton2 {

    private static Singleton2 INSTANCE = null;

    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2() {}

    public static Singleton2 getInstance() {
        return INSTANCE;
    }
}

Generally speaking, they are instantiated when the class is loaded, so there is basically no difference between the two writing methods. They are both hungry Chinese singletons.

Lazy single case

The lazy type is much more complicated than the hungry type. First look at a relatively simple way to write

/**
 * Lazy singleton, thread unsafe
 * @author sicimike
 * @create 2020-02-23 20:21
 */
public class Singleton3 {

    private static Singleton3 INSTANCE = null;

    private Singleton3() {}

    public static Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }
}

When the program calls the getInstance() method for the first time, the INSTANCE is created. The above code has no problem in A single threaded environment, but it is thread unsafe in A multi-threaded environment. The reason is also very simple. Suppose that thread A executes to line 14 (not yet executed) and INSTANCE = new Singleton3(), then INSTANCE is still equal to null, and thread B will also enter if judgment. If two threads continue to execute, two different instances will be generated, so the thread is not safe. To achieve thread safety is also very simple, because we know that there is A keyword called synchronized, and slightly modify the code

/**
 * Lazy singleton, thread safety
 * synchronized Keyword implementation
 * @author sicimike
 * @create 2020-02-23 20:26
 */
public class Singleton4 {

    private static Singleton4 INSTANCE = null;

    private Singleton4() {}

    public static synchronized Singleton4 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton4();
        }
        return INSTANCE;
    }
}

There is no doubt that we have implemented thread safe lazy singleton.

However, because the whole getInstance() is locked, the efficiency of this singleton mode is very low. I have also written in previous blogs to minimize the scope of the lock. So instead of locking the whole method, we lock part of the code in the method and transform the code again

/**
 * Lazy singleton, thread unsafe
 * @author sicimike
 * @create 2020-02-23 20:29
 */
public class Singleton5 {

    private static Singleton5 INSTANCE = null;

    private Singleton5() {}

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

Compared with the former one, this implementation is obviously more efficient, because there is a layer of judgment outside the synchronized keyword. As long as the instance is created, it will not enter the synchronization code block, while the former one will enter the synchronization code every time the method is called.

Some students may have questions here. Why does synchronized add an external judgment and an internal judgment?
In fact, this is A common method in concurrent programming, called double check, which is often referred to as double check (bloggers have written the source code of JDK concurrent container and thread pool, and double check is used in many places. Interested students can turn it over). Suppose thread A executes to line 15 (not yet executed) if (INSTANCE == null), at this time, INSTANCE is still equal to null, and thread B may have entered the outer judgment and be blocked in synchronized. Thread A continues to execute and releases the lock after completing the creation of the object. Thread B obtains the lock and enters the synchronization code block. If there is no second judgment, thread B will directly create the object. Therefore, A judgment must also be added in synchronized.

This implementation seems seamless, but it is still thread unsafe.
The root cause of thread insecurity is that INSTANCE = new Singleton5() is not an atomic operation. It is done in three steps
1. Allocate memory to this object
2. Initialize this object
3. Point the INSTANCE variable to the initialized object
Normally, it is executed in the order of 1 - > 2 - > 3, but 2 and 3 may be reordered, and the execution order becomes 1 - > 3 - > 2. If it is executed in the order of 1 - > 3 - > 2. Thread A has finished executing 3. At this time, the object has not been initialized, but the INSTANCE variable is no longer null. When thread B executes the if judgment outside the synchronized keyword, it returns directly. At this time, thread B gets an object that has not been initialized, which may cause security risks. Therefore, this implementation is thread unsafe. To solve this problem, that is, to solve the problem of reordering, you should think of another keyword volatile. For unfamiliar students, please read the previous articles of the blogger: volatile keyword explanation , modify the code again

/**
 * Lazy singleton, thread safety
 * Double check lock
 * @author sicimike
 * @create 2020-02-23 20:34
 */
public class Singleton6 {

    private static volatile Singleton6 INSTANCE = null;

    private Singleton6() {}

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

Adding volatile keyword to INSTANCE solves this problem. This implementation method is the way of double check lock. Volatile keyword has two functions here:

  • The problem of reordering is solved
  • It ensures that the modification of INSTANCE can be known by other threads in time

The implementation of double check lock involves more knowledge, so relatively speaking, there is a simpler way, that is, using static internal classes

/**
 * Lazy singleton, thread safety
 * Static inner class
 * @author sicimike
 * @create 2020-02-23 20:36
 */
public class Singleton7 {

    private Singleton7() {}

    public static Singleton7 getInstance() {
        return InnerClass.INSTANCE;
    }

    private static class InnerClass {
        private static Singleton7 INSTANCE = new Singleton7();
    }

}

This implementation method not only meets lazy loading, but also meets thread safety. The amount of code is still small. It is a relatively elegant implementation method.

So far, seven writing methods of singleton mode have been given, and five are thread safe. Although it is a bit similar to "fennel beans have several ways of writing fennel characters", it will be a great harvest to carefully understand the differences between various ways of writing and the problem of thread safety.

Singleton and serialization

In the data processing of computer science, serialization refers to the process of converting the data structure or object state into a usable format (e.g. stored as a file, stored in a buffer, or sent through the network) for subsequent recovery in the same or another computer environment. When the byte result is retrieved according to the serialization format, it can be used to produce a copy with the same semantics as the original object.

In short, the process of transforming the state of data structure or object into storable or transportable is called serialization, and the opposite process is called deserialization. At first glance, serialization has nothing to do with singleton. So let's take a look at an example. First, use the first hungry man singleton method to write a singleton to implement the Serializable interface

/**
 * Singleton pattern and serialization
 * @author sicimike
 * @create 2020-02-23 22:26
 */
public class SingletonWithSerialize implements Serializable {

    private static final long serialVersionUID = 6133201454552796162L;

    private static final SingletonWithSerialize INSTANCE = new SingletonWithSerialize();

    private SingletonWithSerialize() {}

    public static SingletonWithSerialize getInstance() {
        return INSTANCE;
    }

}

Next, serialize and deserialize this object

/**
 * @author sicimike
 * @create 2020-02-23 20:48
 */
public class SingletonDemo {

    private static final String FILE_PATH = "singleton.data";

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SingletonWithSerialize instance = SingletonWithSerialize.getInstance();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(FILE_PATH)));
        oos.writeObject(instance);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(FILE_PATH)));
        SingletonWithSerialize readInstance = (SingletonWithSerialize) ois.readObject();

        System.out.println(instance);
        System.out.println(readInstance);

        // Close IO stream
    }

}

results of enforcement

com.sicimike.creation.singleton.SingletonWithSerialize@7f31245a
com.sicimike.creation.singleton.SingletonWithSerialize@7cca494b

As you can see, serialization and deserialization break the singleton pattern.

JDK has already provided a solution to this problem, which is to provide a readResolve method in the singleton class

The readResolve method is called when ObjectInputStream has read an object from the stream and is preparing to return it to the caller. ObjectInputStream checks whether the class of the object defines the readResolve method. If the method is defined, the readResolve method is called to allow the object in the stream to designate the object to be returned.
-https://docs.oracle.com/javase/8/docs/platform/serialization/spec/input.html#a5903

When ObjectInputStream reads an object from the stream and is ready to return it to the caller, the readResolve method is called. ObjectInputStream checks whether the class of the object defines the readResolve method. If this method is defined, the readResolve method is called to allow the objects in the flow to specify the objects to return.

In other words, during deserialization, JDK provides a hook function to let developers specify the object to be returned. The usage is as follows

/**
 * Singleton pattern and serialization
 * @author sicimike
 * @create 2020-02-23 22:26
 */
public class SingletonWithSerialize implements Serializable {

    private static final long serialVersionUID = 6133201454552796162L;

    private static final SingletonWithSerialize INSTANCE = new SingletonWithSerialize();

    private SingletonWithSerialize() {}

    public static SingletonWithSerialize getInstance() {
        return INSTANCE;
    }

	// Solve the problem that serialization and deserialization break the singleton pattern
    private Object readResolve() {
        return this.INSTANCE;
    }

}

results of enforcement

com.sicimike.creation.singleton.SingletonWithSerialize@6d6f6e28
com.sicimike.creation.singleton.SingletonWithSerialize@6d6f6e28

You can see that after adding the readResolve method, the singleton mode is completed again

Single case and reflex

At first glance, singleton mode has nothing to do with reflection. However, when you think about it carefully, the method of creating objects described above is realized through the new keyword. In fact, you can also create objects by reflection. Take the first hungry Han style as an example

/**
 * @author sicimike
 * @create 2020-02-23 20:48
 */
public class SingletonDemo {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class<Singleton1> classObject = Singleton1.class;
        Constructor<Singleton1> constructor = classObject.getDeclaredConstructor();
        constructor.setAccessible(true);

        // Singleton mode acquisition
        Singleton1 instance = Singleton1.getInstance();
        // Reflection acquisition
        Singleton1 reflectInstance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(reflectInstance);
    }

}

results of enforcement

com.sicimike.creation.singleton.Singleton1@4554617c
com.sicimike.creation.singleton.Singleton1@74a14482

You can see that the object created through the singleton class is not the same as the object created through reflection, and reflection can create instances at will, which destroys the singleton mode.

Eighth single case

The seven singletons mentioned above will be destroyed in varying degrees by serialization / deserialization and reflection. To solve serialization / deserialization, we rely on the hook function readResolve provided by JDK. There are also some ways to solve reflection, that is, add judgment in the private constructor, and throw an exception if INSTANCE is not null

To solve the problems of serialization / deserialization and reflection more gracefully, there is another more elegant way to write, that is, using enumeration

/**
 * Enumeration implementation, singleton, thread safety
 * @author sicimike
 * @create 2020-02-23 20:46
 */
public enum Singleton8 {
    INSTANCE;

    public static Singleton8 getInstance() {
        return INSTANCE;
    }

}

Then test the reflection

/**
 * @author sicimike
 * @create 2020-02-23 20:48
 */
public class SingletonDemo {

    private static final String FILE_PATH = "singleton.data";

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class<Singleton8> classObject = Singleton8.class;
        Constructor<Singleton8> constructor = classObject.getDeclaredConstructor();
        constructor.setAccessible(true);

        // Singleton mode acquisition
        Singleton8 instance = Singleton8.getInstance();
        // Reflection acquisition
        Singleton8 reflectInstance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(reflectInstance);

    }
}

The implementation will get the following results

Exception in thread "main" java.lang.NoSuchMethodException: com.sicimike.creation.singleton.Singleton8.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.sicimike.creation.singleton.SingletonDemo.main(SingletonDemo.java:31)

Before explaining this result, let's take a look at the decompiled code of singleton 8 (compiler: jad download page)

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Singleton8.java

package com.sicimike.creation.singleton;

public final class Singleton8 extends Enum
{

    public static Singleton8[] values()
    {
        return (Singleton8[])$VALUES.clone();
    }

    public static Singleton8 valueOf(String name)
    {
        return (Singleton8)Enum.valueOf(com/sicimike/creation/singleton/Singleton8, name);
    }

    private Singleton8(String s, int i)
    {
        super(s, i);
    }

    public static Singleton8 getInstance()
    {
        return INSTANCE;
    }

    public static final Singleton8 INSTANCE;
    private static final Singleton8 $VALUES[];

    static 
    {
        INSTANCE = new Singleton8("INSTANCE", 0);
        $VALUES = (new Singleton8[] {
            INSTANCE
        });
    }
}

For decompiled code, you only need to pay attention to three points:

  • It inherits Java. Net by default Lang. enum class
  • A private constructor Singleton8(String s, int i) is generated, which is not parameterless
  • The INSTANCE is created in a static code block

Now we know why the above instance will report NoSuchMethodException exception. Since there is no parameterless constructor, there is a constructor with two parameters. Then we will modify the code again and call the constructor with two parameters

/**
 * @author sicimike
 * @create 2020-02-23 20:48
 */
public class SingletonDemo {

    private static final String FILE_PATH = "singleton.data";

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class<Singleton8> classObject = Singleton8.class;
        Constructor<Singleton8> constructor = classObject.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);

        // Singleton mode acquisition
        Singleton8 instance = Singleton8.getInstance();
        // Reflection acquisition
        Singleton8 reflectInstance = constructor.newInstance("Sicimike", 18);
        System.out.println(instance);
        System.out.println(reflectInstance);
    }
}

The implementation results are as follows

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.sicimike.creation.singleton.SingletonDemo.main(SingletonDemo.java:59)

That is, enumeration types do not support creating instances through reflection at all.

So far, the singleton pattern implemented by enumerating types perfectly solves the problems of serialization / deserialization and reflection. This method is also recommended by Joshua Bloch.

However, through the decompilation results, we can also see that the enumeration class creation object is also completed in the static code block, that is, the class loading stage. Therefore, the singleton pattern of enumeration type implementation should belong to hungry Chinese singleton.

summary

The first seven methods are mentioned in this article. Some of them can avoid being destroyed by reflection by adding judgment logic to the construction method. Interested students can try.

Singleton mode is very common in the interview, because handwriting a singleton mode is faster and can be done in a few minutes. Singleton mode is the simplest design mode and the most complex design mode, which involves many knowledge points. I feel that when the interviewer asks you to write the singleton mode, what you most want to see should be the kind of double check lock, that is, singleton 6. Because it involves the most knowledge points: JVM, multithreading, lock, volatile, serialization, reflection and so on.

Author: Sicimike

last

The knowledge points involved in the article have been sorted into materials, and videos have been recorded for everyone to download and learn. They are free to share. I am full of sincerity. I hope I can help friends who develop in this industry. I can spend less time looking for materials in forums, blogs and other places, and really spend my limited time on learning, so I share these materials. I believe that for friends who have worked and encountered technical bottlenecks, there must be something you need in this material.

Free access to information: click here to get technical information on Dubbo, Redis, Netty, zookeeper, Spring cloud, distributed, high concurrency and other architectures for free

Add judgment logic to the construction method to avoid being damaged by reflection. Interested students can try.

Singleton mode is very common in the interview, because handwriting a singleton mode is faster and can be done in a few minutes. Singleton mode is the simplest design mode and the most complex design mode, which involves many knowledge points. I feel that when the interviewer asks you to write the singleton mode, what you most want to see should be the kind of double check lock, that is, singleton 6. Because it involves the most knowledge points: JVM, multithreading, lock, volatile, serialization, reflection and so on.

Author: Sicimike

last

The knowledge points involved in the article have been sorted into materials, and videos have been recorded for everyone to download and learn. They are free to share. I am full of sincerity. I hope I can help friends who develop in this industry. I can spend less time looking for materials in forums, blogs and other places, and really spend my limited time on learning, so I share these materials. I believe that for friends who have worked and encountered technical bottlenecks, there must be something you need in this material.

[external chain picture transferring... (img-m4EEop1V-1623727786020)]

Free access to information: click here to get technical information on Dubbo, Redis, Netty, zookeeper, Spring cloud, distributed, high concurrency and other architectures for free

Keywords: Java Interview Programmer

Added by paulnaj on Sat, 29 Jan 2022 15:02:55 +0200