The use of singleton mode

The use of singleton mode

1. Introduction

单例模式: Belongs to the creational pattern and involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its only object , directly , without instantiating an object of this class.

There are two types of singleton design patterns:

  • 饿汉式: Class loading will cause the single instance object to be created.

  • 懒汉式: Class loading will not cause the single instance object to be created, but will only be created when the object is used for the first time.

2. Hungry Chinese style

2.1 Static variable method

This method declares a static variable of the Singleton type at the member position and creates an object instance of the Singleton class. The instance object is created as the class is loaded . If the object is large enough, it will cause a waste of memory if it has not been used .

/**
 * 静态变量创建类的对象
 */
public class Singleton {
    
    
    // 私有类的无参构造
    private Singleton() {
    
    }
    // 给本类创建一个新的对象,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance = new Singleton();
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance(){
    
    
        return instance;
    }
}

public static void main(String[] args) {
    
    
          // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

2.2 Static code block method

This method declares a static variable of the Singleton type at the member position, and the creation of the object is in the static code block , which is also created for the loading of the class . Therefore, it is basically the same as the static variable method of Hungry Man, but of course this method also has the problem of memory waste .

/**
 * 静态变量创建类的对象
 */
public class Singleton {
    
    
    // 私有的构造方法
    private Singleton(){
    
    }
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance;//null
    // 在静态代码块中进行赋值,创建Singleton的对象
    static{
    
    
        instance = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
    
    
        return instance;
    }
}

public static void main(String[] args) {
    
    
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

2.3 Enumeration method

The enumeration class implements the singleton pattern is a highly recommended singleton implementation pattern, because the enumeration type is thread-safe and will only be loaded once , and the enumeration type is the only singleton that cannot be destroyed in the singleton implementation used. Example implementation pattern.

/*
* 枚举方式
* 枚举方式属于恶汉式方式
**/
public enum Singleton {
    
    
    INSTANCE;
}

public static void main(String[] args) {
    
    
    // 重复调用Singletion枚举类的INSTANCE
    Singleton instance1 = Singleton.INSTANCE;
    Singleton instance2 = Singleton.INSTANCE;
    System.out.println(instance1 == instance2);
}

Three, lazy style

3.1 Thread unsafe way

This method declares a static variable of the Singleton type at the member position, and creates the object of the Singleton class when the getInstance() method is called to obtain the object of the Singleton class, thus achieving the effect of lazy loading . However, in a multithreaded environment, thread safety issues arise .

/**
 * 线程不安全
 */ 
public class Singleton {
    
    
    // 私有的构造方法
    private Singleton(){
    
    }
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance;//null
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
    
    
        // 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
        // 如果不为空,说明已将创建过,返回对象即可
        if(instance == null){
    
    
            instance = new Singleton();
        }
        return instance;
    }
}

public static void main(String[] args) {
    
    
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

3.2 Thread-safe way

This method also achieves the effect of lazy loading, and at the same time solves the problem of thread safety . However, it is added to the getInstance() method synchronized关键字, which causes the execution effect of this method to be particularly low. The thread safety problem will only appear when the instance is initialized, and the problem will not exist once the initialization is completed.

/**
 * 线程安全
 */ 
public class Singleton {
    
    
    // 私有的构造方法
    private Singleton(){
    
    }
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance;//null
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static synchronized Singleton getInstance() {
    
    
        // 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
        // 如果不为空,说明已将创建过,返回对象即可
        if(instance == null){
    
    
            // 加锁前,线程1、2都进入到该位置,此时就会出现创建多个对象的情况
            // 加锁后,线程1进入,线程2会在外面等待线程1运行完毕,释放锁后才继续执行
            instance = new Singleton();
        }
        return instance;
    }
}

public static void main(String[] args) {
    
    
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

3.3 Double check lock method

This method makes a change to the problem of locking in lazy man mode. For getInstance()the method , most of the operations are read operations , and read operations are thread-safe , so it is not necessary for each thread to hold a lock to call this method. method, we need to adjust the timing of locking .

In the case of multi-threading , a null pointer problem may occur . The reason for the problem is that the JVM will perform optimization and instruction reordering operations when instantiating objects . To solve the problem of null pointer exception caused by the double-check lock mode, you only need to use volatilethe keyword , volatilewhich can ensure visibility and order .

/*
* 双重检查锁
* 添加 volatile关键字之后的双重检查锁模式是一种比较好的单例
* 实现模式,能够保证在多线程的情况下线程安全也不会有性能问题
**/
public class Singleton {
    
    
    // 私有的构造方法
    private Singleton(){
    
    }
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static volatile Singleton instance;//null
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
    
    
        // 第一次判断,判断instance是否为null,如果为null,说明还没有创建Singleton类的对象,不需要抢占锁
        // 如果不为空,说明已将创建过,返回对象即可,读操作直接返回,没有加锁
        if(instance == null){
    
    
            // 第二次判断,写操作需要加锁
            synchronized (Singleton.class) {
    
    
                if (instance == null) {
    
    
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

public static void main(String[] args) {
    
    
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

3.4 Static inner class method

The instance of this method is created by the internal class . Since the JVM will not load the static internal class during the process of loading the external class, only the properties/methods of the internal class will be loaded and its static properties will be initialized . Static properties are guaranteed to be instantiated only once due to being staticmodified , and the order of instantiation is strictly guaranteed .

The static inner class method ensures thread safety without any locks , and there is no performance impact and space waste .

/*
* 静态内部类方式
* 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
**/
public class Singleton {
    
    
    // 私有的构造方法
    private Singleton(){
    
    }
    //定义一个静态内部类
    private static class SingletonHolder {
    
    
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
    
    
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

public static void main(String[] args) {
    
    
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

Fourth, destroy the singleton mode

4.1 Serialization destruction

The result of running the code is falsethat serialization and deserialization have broken the singleton design pattern.

The value in the object is the same, but the reference of the object is different, which is equivalent to two objects, involving deep copy

/**
 * 静态内部类方式的单列模式
 */
public class Singleton implements Serializable {
    
    
    // 私有的构造方法
    private Singleton(){
    
    }
    //定义一个静态内部类
    private static class SingletonHolder {
    
    
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
    
    
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

public class Client {
    
    
    public static void main(String[] args) throws Exception {
    
    
//        writeObjectToFile();
        Singleton s1 = readObjectFromFile();
        Singleton s2 = readObjectFromFile();
        System.out.println(s1 == s2);
    }
    // 使用序列化向文件中写数据(对象)
    private static void writeObjectToFile() throws IOException {
    
    
        // 获取Singleton对象
        Singleton instance = Singleton.getInstance();
        // 创建对象输出流对象
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
        // 将对象写入
        objectOutputStream.writeObject(instance);
        // 释放资源
        objectOutputStream.close();
    }
    // 使用反序列化读取文件的数据(对象)
    private static Singleton readObjectFromFile() throws Exception {
    
    
        // 创建对象输入流对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
        // 读取对象
        Singleton instance = (Singleton) objectInputStream.readObject();
        System.out.println(instance);
        // 释放资源
        objectInputStream.close();
        return instance;
    }
}

4.2 Serialization destruction solution

To solve the destruction of the singleton pattern by serialization and deserialization, we need to add readResolve()a method to the Singleton class, call it when it is deserialized, and return the value (object) of this method. The principle is that in the underlying implementation of the readObject method of the input stream object, if the input is found to be an object type, the method will be called, and the method will be readOrdinaryObjectautomatically executed hasReadResolveMethodto determine whether there is readResolvea method in the class, and if so, it will be executed invokeReadResolvemethod to obtain the instance specially provided in the class, so as to solve the problem that deserialization destroys the singleton pattern.

// 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve(){
    
    
    return SingletonHolder.INSTANCE;
}

4.3 Reflection Destruction

The result of running the code is falsethat reflection has broken the singleton design pattern.

/**
 * 静态内部类方式的单列模式
 */
public class Singleton implements Serializable {
    
    
    // 私有的构造方法
    private Singleton(){
    
    }
    //定义一个静态内部类
    private static class SingletonHolder {
    
    
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
    
    
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

public class Client {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 获取Singleton字节码对象
        Class<Singleton> singletonClass = Singleton.class;
        // 获取无参构造方法对象
        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
        // 取消访问检查
        declaredConstructor.setAccessible(true);
        // 创建Singleton对象
        Singleton s1 = (Singleton) declaredConstructor.newInstance();
        Singleton s2 = (Singleton) declaredConstructor.newInstance();
        // 结果返回false,说明反射破坏了单例模式
        System.out.println(s1 == s2);
    }
}

4.4 Solution to reflection destruction

When the constructor is created by calling the reflection method, an exception is thrown directly. Do not run this operation.

/**
 * 静态内部类方式的单列模式
 */
public class Singleton {
    
    
    // 定义一个flag判断是否已经创建过对象
    private static boolean flag = false;
    // 给私有构造方法添加锁,预防线程安全问题
    private Singleton(){
    
    
        synchronized(Singleton.class){
    
    
            // flag为false则跳过,直接正常创建,若flag为true,说明已经创建过,抛出异常
            if(flag){
    
    
                throw new RuntimeException("不能创建多个对象");
            }
            // 第一次创建后,应该将flag设置为true
            flag = true;
        }
    }
    //定义一个静态内部类
    private static class SingletonHolder {
    
    
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
    
    
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

记录每一个学习瞬间

Guess you like

Origin blog.csdn.net/qq_51601665/article/details/131019523