Singleton of design pattern

Posted by amethyst42 on Fri, 19 Nov 2021 08:47:36 +0100

There are six principles and twenty-three design patterns in the design pattern.
The six principles are: single responsibility principle, opening and closing principle, Richter substitution principle, dependency inversion principle, interface isolation principle and Dimitri principle.
Twenty three design patterns: Singleton pattern, Builder pattern, prototype pattern, factory method pattern, abstract factory pattern, policy pattern, state pattern, responsibility chain pattern, Interpreter pattern, command pattern, observer pattern, memo pattern, iterator pattern, template method pattern, visitor pattern, mediation pattern, agent pattern, composition pattern, adapter pattern Decoration mode, element sharing mode, appearance mode and bridging mode.
Now let's introduce Singleton.

Singleton mode

Singleton pattern should be the most used design pattern in our development. Both experienced and novice programmers should have used it.

definition

A class has only one instance, and it instantiates itself to provide the whole system.

Usage scenario

Ensure that a class has only one object in the project to avoid wasting resources by generating too many objects. For example:

  • Tools (time conversion, picture loading, etc.)
  • Network request IO operation, etc

realization

There are many ways to implement the singleton mode, such as:

  • Lazy - thread unsafe
  • Lazy - thread safe
  • Hungry man way
  • Double check lock type
  • Registration type
  • enumeration

Lazy - thread unsafe

The most basic implementation method is the thread context singleton, which does not need to be shared with all threads, nor does it need to add locks such as synchronize to improve performance.

  • Example
/**
 * Singleton mode
 */
public class Singleton {
    private static Singleton instance;
    // Lazy - thread unsafe
    public static Singleton getInstance1(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

However, it has a fatal disadvantage, that is, when getInstance1() is called simultaneously in two same threads, different Singleton objects will be generated in the two threads. The role of a single case is quite useless. Because its thread is not safe, it has the following method.

Lazy - thread safe

Coupled with the lazy mode based on thread safety such as synchronize, the relative performance is very low, and synchronization is not required most of the time.

  • Example
/**
 * Singleton mode
 */
public class Singleton {
    private static Singleton instance;
    // Lazy - thread safe
    public static synchronized Singleton getInstance2(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

In this way, it is thread safe; However, because it is a synchronous method, it will be synchronized when called by multiple threads, resulting in low efficiency. In order to improve efficiency, we have the following methods

Hungry man way

Refers to the global singleton instance, which is built when the class is loaded. It is inherently thread safe.

  • Example
/**
 * Singleton mode
 */
public class Singleton {
    // Hungry Han style
    private static Singleton instance2 = new Singleton();
    public static Singleton getInstance3(){
        return instance2;
    }
}

It is thread safe, but if I don't use this class all the time, it will occupy resources and will not be released because it has already been instantiated when the class is initialized. In view of this situation, there are the following ways.

Double check lock type

On the lazy basis, the synchronize keyword and volatile keyword are used to ensure that there is no competition between threads and multiple instances are generated during the first creation. Only the first creation is synchronized, and the performance is relatively high

  • Example
/**
 * Singleton mode
 */
public class Singleton {
    // Double check lock type
    private static volatile Singleton instance3;
    public static Singleton getInstance3(){
        if (null == instance3){
            synchronized (Singleton.class){
                if (null == instance3){
                    instance3 = new Singleton();
                }
            }
        }
        return instance3;
    }
}

It is the most commonly used singleton mode in our development process. It is thread safe and more efficient, but it reacts slowly when it is loaded for the first time and occasionally fails to load. There will also be some defects under the high parallel, although the probability is very small. In order to optimize it, the following methods appear.

Registration type

It exists as a global attribute of the creation class and is created when the creation class is loaded.

  • Example
/**
 * Singleton mode
 */
public class Singleton {

    // Registration type
    public static Singleton getInstance4(){
        return SingletonHolder.instance;
    }
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
}

instance will not be initialized when Singleton is recorded for the first time. It will only be instantiated when getInstance4() is called for the first time. It not only ensures thread safety, but also ensures the uniqueness of objects, but also delays the instantiation of singletons. so it is also the most recommended Singleton mode
This is also called static inner class singleton mode. There is also a container singleton mode.

  • Example
/**
 * Singleton mode
 */
public class Singleton {
    // Registered container singleton mode
    private static Map<String, Object> singletonMap = new HashMap<String, Object>();
    public static Object getInstance5(Class clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String className = clazz.getName();
        if (ObjectUtils.isEmpty(className)){
            className = Singleton.class.getName();
        }
        if (!singletonMap.containsKey(className) || null == singletonMap.get(className)){
            singletonMap.put(className, (Singleton)Class.forName(className).newInstance());
        }
        return singletonMap.get(className);
    }
}

In this way, multiple singleton types can be managed in a unified map, reducing the cost of our use. It also hides the specific implementation from users. Reduce coupling.
Finally, let's enumerate single examples

enumeration

Enumerating classes in java is also a singleton mode. The most important thing is that the creation of enumeration instance is thread safe, and it is a singleton in any case.

  • Example
/**
 * Enumeration singleton
 */
public enum SingletonEnum {
    INSTANCE;
    public void method(){
        System.out.println("singleton");
    }
}
public static void main(String[] args) {
    SingletonEnum.INSTANCE.method();
}

This method is rarely used, but it is really easy to use. There is no need to worry about serialization and deserialization. For other singleton deserialization, you need to add a readResolve() function to return the singleton instance.

    private Object readResolve(){
        return instance;
    }

readResolve() is the hook function provided by deserialization. Prevent the singleton from re creating a new object during deserialization.

summary

The singleton pattern is a design pattern often used in our development. We should select the singleton according to the actual needs instead of blindly following the trend. Learn more about design patterns to write elegant code.
demo

reference resources:
Baidu Encyclopedia - single case mode
Android source code design pattern analysis and Practice

Topics: Java Android Design Pattern