I'll be blunt, you may not know the singleton pattern that well

singleton pattern

 

The sun has not failed us, and we must not fail the sun. --Shen Congwen

The following jingles also exist in the code world:

I'm single, I'm proud, and I'm saving the country.
I'm single, I'm proud, I save rubber for my motherland.

Although the singleton mode is simple, there are not many people who really understand it. Today, we challenge the singleton mode, the most complete classic design pattern in the whole network.

1. Singleton pattern

definition

Make sure a class has absolutely only one instance in any case, and provide a global point of access.
hide its constructor
Belongs to the creational design pattern

Applicable scene

Make sure there is absolutely only one instance in any case
ServletContext,ServletConfig,ApplicationContext,DBPool

2. Hungry Chinese Singleton

definition

It is loaded when the system is initialized, regardless of whether the singleton is used or not.

advantage

High execution efficiency, high performance, without any locks

shortcoming

In some cases, memory may be wasted
Can be destroyed by reflection

code

public class HungrySingleton {

    private static final HungrySingleton singleton = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance() {
        return singleton;
    }
}

3. Lazy singleton

definition

Instances are not created when the system is initialized, and instances are created only when they are used.

advantage

saves memory

shortcoming

synchronized causes poor performance
Can be destroyed by reflection

3.1 Method lock writing

code

public class LazySingleton {

    private static LazySingleton singleton = null;

    private LazySingleton(){}


    /**
     * version 1
     * @return
     */
    private synchronized LazySingleton getInstance() {
        if (null == singleton) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

3.2 Code block lock writing method

code

public class LazySingleton {

    private static LazySingleton singleton = null;

    private LazySingleton(){}
    
    /**
     * Version 2 is slightly more optimized than version 1
     * @return
     */
    private  LazySingleton getInstance() {
        synchronized (LazySingleton.class) {
            if (null == singleton) {
                singleton = new LazySingleton();
            }
        }
        return singleton;
    } 
}

3.3 Double judgment and lock writing

trap case

public class LazySingleton {

    private static LazySingleton singleton = null;

    private LazySingleton(){}
    
    /**
     * Version 3 Double Judgment
     * @return
     */
    private  LazySingleton getInstance() {
        if (null == singleton) {
            synchronized (LazySingleton.class) {
                if (null == singleton) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

Version 3 seems to be a lot more optimized than version 2, but in fact, this double judgment has a huge loophole trap in the production environment, that is, the reordering of instructions. If you need to know, you can leave a message in the comment area. The solution is also very simple, the volatile keyword. It can limit instruction reordering.

Correct spelling

public class LazySingleton {

    private volatile static LazySingleton singleton = null;

    private LazySingleton(){}
    
    /**
     * Version 3 Double Judgment
     * @return
     */
    private  LazySingleton getInstance() {
        if (null == singleton) {
            synchronized (LazySingleton.class) {
                if (null == singleton) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

The advantages of double judgment: high performance, thread safety.
Disadvantages: The code is extremely readable and not elegant.

3.4 Writing static inner classes

Using the order in which the JVM loads classes, static inner classes are loaded only when the outer class uses the static inner class.

advantage

Elegantly written, using Java's grammatical features, high performance, and avoiding memory waste

shortcoming

Can be destroyed by reflection

public class LazyStaticInnerSingleton {

    private LazyStaticInnerSingleton(){}

    public static LazyStaticInnerSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

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

This way of writing should be elegant and perfect, but it has a disadvantage that it can be destroyed by reflection. At the end of the article, I will prove what can be destroyed by reflection. Is there any way to write this so that this singleton will not be destroyed by reflection? The answer is yes!

public class LazyStaticInnerSingleton {

    private LazyStaticInnerSingleton(){
        if (null != LazyHolder.INSTANCE) {
            throw new RuntimeException("Illegal access is not allowed!");
        }
    }

    public static LazyStaticInnerSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

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

This way of writing solves the problem of being destroyed by reflection. But it doesn't look so elegant.

4. Registered singleton

definition

Cache each instance in a unified container and use a unique identifier to get the instance.

4.1. Enumeration writing registered singleton

advantage

Elegantly written, thread-safe

shortcoming

Similar to the hungry Chinese style, extensive use will cause memory waste, and the fundamental reason lies in the characteristics of the enumeration itself.

public enum  EnumSingleton {
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

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

Instructions

public class Test {

    public static void main(String[] args) {
        EnumSingleton singleton = EnumSingleton.getInstance();
        singleton.setData(new Object());
        singleton.getData();
    }
}

4.2. Spring IOC container registered singleton

Spring designers combined the writing method and characteristics of enumerated singleton to write their own IOC container registered singleton.

public class ContainerSingleton {

    private ContainerSingleton() {}

    private static Map<String, Object> ioc = new ConcurrentHashMap<>();

    public Object getInstance(String className) {
        if (!ioc.containsKey(className)) {
            Object instance = null;
            try {
                instance = Class.forName(className).newInstance();
            } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            return instance;
        } else {
            return ioc.get(className);
        }
    }
}

5. ThreadLocal singleton

ThreadLocal singleton will definitely use ThreadLocal. According to the characteristics of ThreadLocal itself, that is, the data in the same thread is visible, then this singleton has its own limitations and is rarely used. I have used it when logging in with the token. That is, the front end will pass a token to the back end, and the token can parse out the information of the logged in user. Put the parsed information in ThreadLocal, then this processing request can get the login user information anywhere.

public class ThreadLocalSingleton {
    
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };
    
    private ThreadLocalSingleton() {}
    
    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}

6. Reflection destroys singleton proof

public class Test1 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = HungrySingleton.class;
        Constructor c = clazz.getDeclaredConstructor(null);
        c.setAccessible(true);
        Object o1 = c.newInstance();
        Object o2 = c.newInstance();
        System.out.println(o1 == o2);//will output false
    }
}

The solution is: the constructor throws an exception.

if (null != LazyHolder.INSTANCE) {
    throw new RuntimeException("Illegal access is not allowed!");
}

7. Experts need to know - serialization destroys singletons

First you have to know what serialization is. Serialization is the object in JVM memory, serialized to disk file, and then read into memory. Data interaction between different processes requires serialization before transmission.
All the above singleton patterns solve various problems, but they all have the same problem, that is, they will all be destroyed by serialization. This means that the singletons in the system are serialized to disk and then loaded into memory, so the two singletons before and after serialization are not the same singleton. This is where serialization destroys singletons.
Solution: Add the following method to the singleton:

private Object readResolve() {
    // instead of the object we're on,
    // return the class variable INSTANCE
    return INSTANCE;
}

At last

Thank you for reading this article. If you think the article is useful to you, please click "Follow" above, give a like, or follow the public account "AIO Life", so that you can receive my message as soon as possible. latest articles.

The content of the article is a little bit of my own experience. It is inevitable that there is something wrong. Welcome to discuss in the comment area below. Your attention is the driving force for me to create high-quality articles.

 

Posted by Michiel on Sat, 07 May 2022 03:15:44 +0300