Design pattern-do you really know the singleton pattern?

Design pattern (Design pattern) is a set of code design experience that is used repeatedly and known to most people, after classification and cataloging. The use of design patterns is to reusable code, make it easier for others to understand, and ensure code reliability. There is no doubt that design patterns are mutually beneficial to others and systems. Design patterns make code preparation truly engineering. Design patterns are the cornerstone of software engineering, just like the bricks of a building. The reasonable use of design patterns in the project can perfectly solve many problems. Each pattern has a corresponding principle to correspond to it now. Each pattern describes a recurring problem around us, and the core of the problem. Solution, which is why it can be widely used. Simply put, a pattern is a general solution to a certain type of problem in certain scenarios.

Generally speaking, the 23 design patterns can be roughly divided into three categories: creational patterns , structural patterns , and behavioral patterns .

Creation mode : the mode of object instantiation, which is used to decouple the instantiation process of objects.

Structural mode : Combine classes or objects together to form a larger structure.

Behavioral patterns : how classes and objects interact, and divide responsibilities and algorithms.

Today we will first talk about the singleton mode in the creation mode .

What is the singleton pattern

The singleton mode is also a common problem in interviews. Although many people know it, many people are not familiar with all the writing methods, as well as the writing problems and solutions.

The singleton mode has three typical characteristics: 1. There is only one instance. 2. Self-instancing. 3. Provide global access points.

Therefore, when only one instance object is needed in the system or only one public access point is allowed in the system, and the instance cannot be accessed through other access points except this public access point, singleton mode can be used.

The main advantage of the singleton mode is to save system resources and improve system efficiency, while also being able to strictly control customer access to it. Perhaps it is because there is only one instance in the system, which leads to the overload of the singleton class, which violates the "single responsibility principle", and there is no abstract class, so it is difficult to expand.

Various ways of writing singleton mode

1. Hungry Chinese style (recommended)

In this way, private static final Singleton1 INSTANCE = new Singleton1()a static variable is defined. When the JVM loads a class, a singleton will be instantiated. Since a class file will only be loaded once, the JVM can guarantee thread safety.

At the same time, it private Singleton1()is a privatized constructor, which can ensure that this class cannot be new in other classes.

If the external class wants to obtain this instance, it must be obtained through a public static Singleton1 getInstance()method.

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 饿汉式
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton1 {
    
    

    /**
     * 静态变量 启动时自动加载
     */
    private static final Singleton1 INSTANCE = new Singleton1();

    /**
     * 私有化构造器,不能new
     */
    private Singleton1(){
    
    }

    /**
     * 对外暴露一个方法,获取同一个实例
     */
    public static Singleton1 getInstance(){
    
    
        return INSTANCE;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
    
    
        Singleton1 s1 = Singleton1.getInstance();
        Singleton1 s2 = Singleton1.getInstance();

        if(s1 == s2){
    
    
            System.out.println("s1和s2是内存地址相同,是同一个实例");
        }
    }

}

The only problem with this approach is that it will be instantiated if it is not used. If you don't find faults, it's not really a problem.

This method is simple and practical, and I highly recommend it to be used in actual projects .

2. Lazy man style one

The lazy man style is to solve the above problem of loading the hungry man style regardless of whether it is used or not.

The so-called lazy loading means that when it is used, it is initialized when it is used, and it will not be loaded when it is not used.

Use private static volatile Singleton2 INSTANCEdefined variables, but will not initialize the instance. The variable is initialized getInstance()when the method is called .

package com.wuxiaolong.design.pattern.singleton;

import java.util.HashSet;
import java.util.Set;

/**
 * Description: 懒汉式一
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton2 {
    
    
    /**
     * 先不初始化   使用volatile的原因见底部
     */
    private static volatile Singleton2 INSTANCE;

    /**
     * 私有化构造器,不能new
     */
    private Singleton2(){
    
    }

    /**
     * 对外暴露一个方法,获取同一个实例。只有实例不存在时才初始化
     * 问题:多线程同时访问getInstance时,可能会new出多个实例
     */
    public static Singleton2 getInstance(){
    
    
        if(INSTANCE == null){
    
    

//            try {
    
    
//                Thread.sleep(10);
//            }catch (Exception e){
    
    
//             e.printStackTrace();
//            }

            INSTANCE = new Singleton2();
        }
        return INSTANCE;
    }

    /**
     * 测试
     * 测试时可以开启 getInstance()方法中的sleep
     */
    public static void main(String[] args) {
    
    

        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    Singleton2 temp = getInstance();
                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(temp.toString());
                }
            }).start();
        }

    }
}

Using the above lazy man also brings many other problems. In the case of multithreaded access getInstance(), there will be thread safety issues and multiple instances will be generated. You can use the maintest above to test it.

In order to solve the thread safety problem, you can use the lock synchronized (static method lock is to lock this class):

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

But this will bring performance problems. In order to improve performance, add a lock to the code block:

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

But this approach still has thread safety issues. Multiple threads can enter the if at the same time. Although only one thread can execute the synchronized code block at the same time, the threads entering the if will all be instantiated.

3. Lazy man style two

In order to solve the thread safety problem of the lazy one above, someone came up with a "smart" technique: Double-Checked Locking (Double-Checked Locking)

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 懒汉式二
 *
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton3 {
    
    
    /**
     * 先不初始化
     */
    private static volatile Singleton3 INSTANCE;

    /**
     * 私有化构造器,不能new
     */
    private Singleton3(){
    
    }

    /**
     * 对外暴露一个方法,获取同一个实例
     * 内部双重判断,使用两次INSTANCE == null的判断
     */
    public static Singleton3 getInstance(){
    
    
        if(INSTANCE == null){
    
    
            synchronized (Singleton3.class){
    
    
                // synchronized块内部,再判断一次是否为空
                if(INSTANCE == null){
    
    
                    INSTANCE = new Singleton3();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
    
    

        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    Singleton3 temp = getInstance();

                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(temp.toString());
                }
            }).start();
        }
    }
}

Double check is to synchronizedjudge once inside and outside separately INSTANCE == null, so that thread safety can be guaranteed:

    public static Singleton3 getInstance(){
    
    
        // 第一次检查
        if(INSTANCE == null){
    
    
            synchronized (Singleton3.class){
    
    
                // synchronized块内部,第二次检查
                if(INSTANCE == null){
    
    
                    INSTANCE = new Singleton3();
                }
            }
        }
        return INSTANCE;
    }

Is it necessary for the first INSTANCE == null above? Of course, if there is no first judgment, every thread will be locked when it comes in, which will be very performance-intensive; with the first judgment, many threads will not get the lock.

This is one of the most perfect writing methods, and it is recommended that you use it in actual projects; using double judgment, there is no thread safety issue.

But personally, it is really unnecessary, just use the hungry man mode. This may be recommended by Virgo.

4. Lazy man type three (recommended)

The static inner class is used here private static class InnerSingleton, which not only ensures lazy loading, but also ensures thread safety.

The reason for lazy loading here is that when jvm loads Singleton4.class, it will not load the static internal class InnerSingleton.class; only the call getInstance()will load the static internal class InnerSingleton.class

The thread safety here is guaranteed by JVM: when jvm loads Singleton4.class, it will only be loaded once; so InnerSingleton.class is only loaded once, so INSTANCE is only instantiated once.

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 
 * 既保证了懒加载,也保证了线程安全。
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton4 {
    
    

    /**
     * 私有化构造器,不能new
     */
    private Singleton4(){
    
    }

    /**
     * 使用静态内部类,jvm加载Singleton4.class的时候,不会加载静态内部类InnerSingleton.class
     * 只有调用getInstance()才会加载静态内部类InnerSingleton.class
     */
    private static class InnerSingleton{
    
    
        private static final Singleton4 INSTANCE = new Singleton4();
    }

    /**
     * 对外暴露一个方法,获取同一个实例
     * 返回的是静态内部类的成员变量,这个变量在调用的时候才会初始化
     */
    public static Singleton4 getInstance(){
    
    
        return InnerSingleton.INSTANCE;
    }


    /**
     * 测试
     */
    public static void main(String[] args) {
    
    
        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    Singleton4 temp = getInstance();
                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(temp.toString());
                }
            }).start();
        }
    }
}

This is also the perfect solution, recommended for wall cracks.

5. Ultimate Mode-Enumeration

Java development knows that a book is called "Effective Java". "Effective Java" was written by Joshua Bloch. He is the founder of the Java Collection Framework, leading the design and implementation of many Java platform features, including JDK 5.0 language enhancements and the award-winning Java Collection Framework. He left SUN in June 2004 and became Google's chief Java architect. In addition, he won the prestigious Jolt Award for the book "Effective Java".

In this book, the ultimate solution is given, using enumerations.

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 
 *  在《Effective Java》这本书中,给出了终极解决方案,使用枚举。
 *  使用枚举这种方式,不仅可以解决线程同步问题,还可以防止反序列化。
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public enum Singleton5 {
    
    

    INSTANCE;

    /**
     * 测试
     */
    public static void main(String[] args) {
    
    
        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(Singleton5.INSTANCE.toString());
                }
            }).start();
        }
    }

}

Using enumeration in this way can not only solve the thread synchronization problem, but also prevent deserialization.

Why does singleton mode prevent serialization and deserialization? In the previous forms, the class can still be instantiated in the form of reflection (classForName). Because enumeration has no construction method, the class cannot be instantiated by reflection.

Although grammatically, this way of writing is the most perfect, but this way I don't like it very much.

Reasons for using volatile keyword modification

In the hungry man mode, member variables INSTANCEare modified with the volatile keyword. What is the reason?

If it is not modified with the volatile keyword, it will cause such a problem:

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

**When the thread executes to the second line, it may happen that although INSTANCE is not null, the object pointed to by INSTANCE has not yet been initialized. **The main reason is reordering. Reordering refers to a means by which the compiler and processor reorder the instruction sequence in order to optimize the performance of the program.
The 5th line of code creates an object, this line of code can be broken down into 3 operations:

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置instance指向刚分配的内存地址

The root is between 2 and 3 in the code, which may be reordered. E.g:

memory = allocate();  // 1:分配对象的内存空间
instance = memory;  // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象

This is not a problem in a single-threaded environment, but there will be problems in a multi-threaded environment: after the instructions of the second and third steps are reordered, the A thread enters the synchronized code block above and executes INSTANCE = new Singleton3()the line that reaches this line. In the third instruction instance = memory, when the B thread comes in INSTANCE != null, it will obtain an object that has not yet been initialized.

The instruction reordering in the second and third steps does not affect the final result of thread A, but it will cause thread B to determine that the instance is not empty and access an uninitialized object.
**So only a small modification (declaring the instance as volatile) can achieve thread-safe delayed initialization. **Because the variable modified by the volatile keyword is forbidden to be reordered.

Follow the official account and enter " java-summary " to get the source code.

Finished, call it a day!

[ Dissemination of knowledge, sharing of value ], thank you friends for your attention and support. I am [ Zhuge Xiaoyuan ], an Internet migrant worker struggling in hesitation.

Guess you like

Origin blog.csdn.net/wuxiaolongah/article/details/107851003