In the Java singleton design pattern, many times we only pay attention to the superficial problems caused by threads, but do not consider the limitations of reflection mechanism. This article aims to briefly introduce the vulnerability of using enumerations to prevent reflection.

One, the most common singleton

Let’s start with one of the most common lazy singletons:

public class Singleton {

    private Singleton(a){} // Private construct

    private static Singleton instance = null// Private singleton

    // Static factory
    public static Singleton getInstance(a){
        if (instance == null) { // Dual detection mechanism
            synchronized (Singleton.class) { / / synchronization locks
                if (instance == null) { // Dual detection mechanism
                    instance = newSingleton(); }}}returninstance; }}Copy the code

The above singleton is written with a double-checking mechanism that adds some security, but does not take into account instruction rearrangements by the JVM compiler.

Second, eliminate the effect of JVM instruction rearrangement on singletons

1. What is order reordering

For example, a simple sentence in Java instance = new Singleton is compiled by the compiler into the following JVM instruction:

memory =allocate(); //1: allocate the object's memory space ctorInstance(memory); //2: initializes the object instance =memory; //3: Sets instance to the newly allocated memory addressCopy the code

However, the order of these instructions is not fixed, and may be optimized by the JVM and CPU to rearrange the instructions in the following order:

memory =allocate(); //1: allocate the memory of the object instance =memory; //3: set instance to the newly allocated memory address ctorInstance(memory); //2: initializes the objectCopy the code

2,

Corresponding to the singleton pattern above, the following problem will occur:

  1. When thread A has finished executing 1,3, it is ready to go 2, i.e. the instance object has not completed initialization but is no longer pointing to null.

  2. If (instance == null) if (instance == null)

  3. Return an instance object that has not been initialized.

3, solve

To prevent this, you can simply use the keyword volatile to modify the instance object.

According to?

Quite simply, the volatile modifier protects instructions from reordering before and after variable access, thereby ensuring that instructions are executed in the correct order.

This means that the order of instruction execution is strictly in accordance with the above 1, 2, 3, so that the object will not appear in the intermediate state.

In fact, the volatile keyword is widely used in multithreaded development.

While nice, the effect of reflection is still not considered here.

Third, the advanced chapter, to achieve a perfect singleton

Episode 1

There are many different ways to implement singletons, but here’s a way to implement singletons using static inner classes:

public class Singleton {

    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton (a){}

    public static Singleton getInstance(a) {
        returnLazyHolder.INSTANCE; }}Copy the code

This is a clever way of doing it because:

  1. The static inner class LazyHolder is not accessible from the outside, and the Singleton INSTANCE is only available when the singleton.getInstance () method is called.

  2. The INSTANCE object is initialized not when the Singleton class is loaded, but when the getInstance method is called so that the static inner class LazyHolder is loaded.

  3. Therefore, this approach is to use the loading mechanism of classloader to achieve lazy loading and ensure the thread-safe construction of singleton.

2. Vulnerability display

A common problem with many singletons is that they cannot prevent the vulnerability of the reflection mechanism to guarantee the uniqueness of the object, as shown in the following example:

Create the object of the singleton constructed above using the following inverse code.

public static void main(String[] args) {

    try {

        // Get the constructor
        Constructor con = Singleton.class.getDeclaredConstructor();

        // Make it accessible
        con.setAccessible(true);

        // Construct two different objects
        Singleton singleton1 = (Singleton)con.newInstance();
        Singleton singleton2 = (Singleton)con.newInstance();

        // Verify that it is a different object
        System.out.println(singleton1);
        System.out.println(singleton2);
        System.out.println(singleton1.equals(singleton2));
    } catch(Exception e) { e.printStackTrace(); }}Copy the code

Let’s go straight to the results:

It turns out, obviously, that these are two objects.

3, solve

Use enumerations to implement the singleton pattern.

The implementation is simple, just three lines of code:

public enum Singleton {
    INSTANCE;
}
Copy the code

The example above is a singleton,

According to?

This is essentially a syntactic sugar for enums, and the JVM prevents reflection from getting the private constructor of an enumerated class.

Still use the above reflection code to test, found, error. Heh heh, perfect solution to the reflection problem.

4 and disadvantages

The use of enumerations serves as a singleton, but there is a downside,

No lazy loading.

Original address: www.jetchen.cn/java-single…