0. Classification of three common design patterns

  • Creation mode: provides a way to hide creation logic while creating objects, giving programs more flexibility in determining which objects need to be created for a given instance. For example:

    • There are four commonly used patterns: factory pattern, abstract factory pattern, singleton pattern and Builder pattern
    • Uncommon: Prototype pattern
  • Structural pattern: Focuses on combinations of classes and objects. The concept of inheritance is used to combine interfaces and define how composite objects acquire new functionality

    • There are four commonly used modes: adapter mode, bridge mode, decorator mode and proxy mode
    • Uncommon: Combination mode, appearance mode, share mode
  • Behavioral patterns: Pay special attention to communication between objects

    • There are 6 commonly used modes: responsibility chain mode, iterator mode, observer mode, state mode, policy mode and template mode
    • Uncommon: Memo mode, command mode
    • Rarely used: visitor pattern, mediator pattern, interpreter pattern

1. Singleton mode (common)

  • Usage scenarios of singleton mode:

    • The business system needs only one object instance globally, such as transmitter, Redis connection object, etc
    • Beans in the Spring IOC container are singletons by default
    • Dependency injection objects in the Controller, Service, and DAO layers of Spring Boot via @autowire are singletons by default
  • Singleton pattern classification:

    • Slacker: This is lazy loading, delaying the creation of objects until they are needed
    • Hungry: As opposed to lazy, create objects ahead of time
  • Implementation steps of singleton pattern:

    • Private constructor
    • Provides methods to get singletons

1.1 Singleton pattern — lazy

The singleton pattern — lazy can be implemented in the following ways:

/ * * *@Auther: csp1999
 * @Date: 2020/11/06 / joined *@Description: singleton design pattern - lazy */
public class SingletonLazy {
    // Create the instance object when it is needed
    private static SingletonLazy instance;

    / constructor private * * * * can't through new SingletonLazy () the way to create an instance of * * when need to use the instance in loading. * only through SingletonLazy getInstance () this way for instance * /
    private SingletonLazy(a) {}/** * method of singleton object */
    public void process(a) {
        System.out.println("Method instantiated successfully!");
    }

    /** ** method one: * <p> * expose a method to obtain the object of the class * <p> * disadvantages: thread is not safe, there are security problems under multithreading **@return* /
    public static SingletonLazy getInstance(a) {
        if (instance == null) {// The instance is created only when the instance is null
            /** * Thread safety issues: * When two or more threads simultaneously determine that instance == NULL is valid * these threads simultaneously execute instantiation within the if judgment * and create more than one instance of SingletonLazy */
            instance = new SingletonLazy();// Instantiate the object as needed
        }
        return instance;
    }

    /** * * Synchronized locking methods has a high performance overhead * because when the internal logic of getInstance2() is complex, under high concurrency conditions * no thread has acquired the right to execute the lock method. It takes time and is inefficient to wait until the complex logic within the method has been executed@return* /
    public static synchronized SingletonLazy getInstance2(a) {
        if (instance == null) {// The instance is created only when the instance is null
            // Synchronized locks can be used to ensure thread safety
            instance = new SingletonLazy();// Instantiate the object as needed
        }
        return instance;
    }

    /** * In the getInstance3() method, locking a partial block of code instead of the entire method ** also has a flaw: *@return* /
    public static SingletonLazy getInstance3(a) {
        if (instance == null) {// The instance is created only when the instance is null
            // Local lock can ensure thread safety, high efficiency
            // Defect: Thread A and thread B are assumed
            synchronized (SingletonLazy.class){
                // Thread B waits for thread A to execute new SingletonLazy(); instantiation
                // When thread A finishes executing, thread B acquires execution authority and can instantiate the object again
                instance = new SingletonLazy();// Instantiate the object as needed}}returninstance; }}Copy the code

Singleton pattern: lazy implementation + double-checked locking + memory model

For the defects of method 3 above, we can use double-checked locking to improve it:

/** * Mode 3 Improved version: * Within the getInstance3() method, locks are applied to code blocks that need to be partially locked, rather than to the entire method * * DCL double-checked Locking keeps high performance in multi-threaded cases * * Is this safe? instance = new SingletonLazy(); The process of instantiating a memory model in the JVM is as follows: * 1. Allocate space to an object * 2. Create an object in space * 3. Assign an object to instance reference * * If the following order occurs: 1 -> 3 -> 2, then the value will be written back to main memory *, other threads will read the latest value of the instance, but this is incomplete object * (instruction reordering) * *@return* /
public static SingletonLazy getInstance3plus(a) {
    if (instance == null) {// The instance is created only when the instance is null
        // Local lock can ensure thread safety, high efficiency
        // Assume threads A and B
        synchronized (SingletonLazy.class){// First check
            // Thread B waits for thread A to execute new SingletonLazy(); instantiation
            // If instance == null is set to null, instance == null is set to null
            // If not, thread B cannot instantiate SingletonLazy
            if (instance == null) {// Second check
                instance = new SingletonLazy();// Instantiate the object as needed}}}return instance;
}
Copy the code

Upgrade mode 3 again to solve the instruction rearrangement problem in the memory model:

// Add the volatile keyword to prevent instruction reordering in the memory model when instantiating objects
private static volatile SingletonLazy instance;

/** * Mode 3 Upgrade the version again: * In the getInstance3() method, lock a block of code that needs to be locked locally, Instead of Locking the whole method * * DCL double-checked Locking keeps high performance in multi-threaded situations * * Addresses instruction reordering -- disallows instruction reordering *@return* /
public static SingletonLazy getInstance3plusplus(a) {
    if (instance == null) {// The instance is created only when the instance is null
        // Local lock can ensure thread safety, high efficiency
        // Assume threads A and B
        synchronized (SingletonLazy.class){// First check
            // Thread B waits for thread A to execute new SingletonLazy(); instantiation
            // If instance == null is set to null, instance == null is set to null
            // If not, thread B cannot instantiate SingletonLazy
            if (instance == null) {// Second check
                instance = new SingletonLazy();// Instantiate the object as needed}}}return instance;
}
Copy the code

Singleton pattern — lazy invocation:

@Test
public void testSingletonLazy(a){
    SingletonLazy.getInstance().process();
}
Copy the code

Extension – What is directive reordering and how to disable it?

What is order reordering? (After learning about JVM, I will share my blog with you)

First of all, there will be instruction rearrangement when the compiler executes instructions, so as to improve the execution speed of instructions. Just like the exam, you must do the good questions first and then do the hard ones.

When will the rearrangement happen?

If there is no dependency between two instructions, for example: A = 1; Y = a + 1. There is no instruction rearrangement between the two statements because y depends on a; So a’s assignment is executed before y’s, but if a =1; Y = 2. These two statements occur because there is no dependency.

  • In the case of single thread, instruction rearrangement does not affect the final result, that is, the consistency of data can be guaranteed.
  • However, under the condition of multi-threading, various threads are executed alternately, the consistency of variables used between two threads cannot be guaranteed.

Let’s look at two cases to understand order reordering:

Case 1:

// Pseudocode: initializes a variable
int x,y = 0;
int a,b = 0;
--------------
// thread one executes:
x=a;
b=1;
--------------
// Thread two executes:Y = b; a=3;
Copy the code
Thread a Thread 2
x=a y=b
b=1 a=3
  • At the end of the execution, if normal, x and y should still be 0;

  • However, since there is instruction rearrangement in the memory model of the memory JVM, thread 1 can execute b=1 first; And then x=a;

  • Similarly, thread 2 can execute a=3 first; Perform y = b;

  • As we all know, in the case of multi-threading, the order of resource preemption between threads is random. Therefore, the following situations cannot be ruled out:

    • When thread one just finished executingb=1;, in thex=a;Thread two runs ahead of the instructiony=b;, then y is assigned the value of 1
    • When thread two just finished executinga=3;, in they=b;Thread 1 executes first before instructionx=a;, then x is assigned 3
    • The final result is x =3, y=1

Case 2:

public class SortSeqDemo {
    int a = 0;
    boolean flag = false;
    
    public void method01(a) {
        a = 1;
        flag = true;
    }
    
    public void method02(a) {
        if (flag) {
            a = a+5; System.out.println(a); }}}Copy the code

In this case, if you execute method01(), a=1; Flag = true; The order in which these two statements are executed is not guaranteed, so another thread executing method02() could end up printing one of two values of a, a = 6 or a = 5.

Volatile eliminates instruction reordering by using memory barriers, a CPU command that serves two purposes: ensuring a specific order of execution, ensuring visibility, and eliminating instruction reordering by adding memory barriers before and after volatile instructions.

Of course, this is just a brief introduction to the JVM memory model and instruction rearrangement, and we will learn more about this topic when we update our JVM learning notes.

1.2 Singleton mode — Hangry

/ * * *@Auther: csp1999
 * @Date: 2020/11/06 / *"@Description: Singleton design pattern - Hungry */
public class SingletonHungry {

    // Instantiate the object directly when the class is loaded
    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry(a){}

    /** * method of singleton object */
    public void process(a) {
        System.out.println("Method instantiated successfully!");
    }

    public static SingletonHungry getInstance(a){
        return instance;// Instantiate the object directly when the class is loaded}}Copy the code

Singleton pattern — Hanhan-style call:

@Test
public void testSingletonHungry(a){
    SingletonHungry.getInstance().process();
}
Copy the code

The Hanchian singleton mode instantiates objects directly when the class is loaded, so there is no need to worry about thread safety.

  • Advantages: Simple implementation, no need to consider thread safety issues
  • Disadvantages: The instance object always occupies this memory, whether or not the instance is used

How to choose between lazy and hungry?

  • If the object has a small memory footprint and is not complex to create, simply use the hungry way
  • In other cases, the lazy way is adopted (preferred)

I will continue to update other design patterns blog posts, if the article is helpful to you, I hope to like and bookmark