El uso del modo singleton

El uso del modo singleton

1. Introducción

单例模式: Pertenece al patrón creacional e involucra una sola clase que es responsable de crear sus propios objetos mientras asegura que solo se crea un único objeto. Esta clase proporciona una forma de acceder a su único objeto , directamente , sin instanciar un objeto de esta clase.

Hay dos tipos de patrones de diseño singleton:

  • 饿汉式: la carga de clases hará que se cree el objeto de instancia única.

  • 懒汉式: La carga de clases no hará que se cree el objeto de instancia única, sino que solo se creará cuando el objeto se use por primera vez.

2. Estilo chino hambriento

2.1 Método de variables estáticas

Este método declara una variable estática del tipo Singleton en la posición del miembro y crea una instancia de objeto de la clase Singleton. El objeto de instancia se crea a medida que se carga la clase . Si el objeto es lo suficientemente grande, provocará un desperdicio de memoria si no se ha utilizado .

/**
 * 静态变量创建类的对象
 */
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 Método de bloque de código estático

Este método declara una variable estática de tipo Singleton en la posición del miembro, y la creación del objeto está en el bloque de código estático , que también se crea para la carga de la clase . Por lo tanto, es básicamente lo mismo que el método de variables estáticas de Hungry Man, pero por supuesto, este método también tiene el problema del desperdicio de memoria .

/**
 * 静态变量创建类的对象
 */
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 Método de enumeración

La clase de enumeración implementa el patrón singleton es un patrón de implementación singleton altamente recomendado, porque el tipo de enumeración es seguro para subprocesos y solo se cargará una vez , y el tipo de enumeración es el único singleton que en la implementación singleton utilizada.no se puede destruir

/*
* 枚举方式
* 枚举方式属于恶汉式方式
**/
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);
}

Tres, estilo perezoso

3.1 Subproceso de manera insegura

Este método declara una variable estática de tipo Singleton en la posición del miembro, y crea el objeto de la clase Singleton cuando se llama al método getInstance() para obtener el objeto de la clase Singleton, logrando así el efecto de carga diferida . Sin embargo, en un entorno de subprocesos múltiples, surgen problemas de seguridad de subprocesos .

/**
 * 线程不安全
 */ 
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 Modo seguro para subprocesos

Este método también logra el efecto de la carga diferida y, al mismo tiempo, resuelve el problema de la seguridad de subprocesos . Sin embargo, se agrega al método getInstance() synchronized关键字, lo que hace que el efecto de ejecución de este método sea particularmente bajo. El problema de seguridad de subprocesos solo aparecerá cuando se inicialice la instancia y el problema no existirá una vez que se complete la inicialización.

/**
 * 线程安全
 */ 
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 Método de bloqueo de doble verificación

Este método hace un cambio en el problema del bloqueo en modo perezoso. Para getInstance()el método , la mayoría de las operaciones son operaciones de lectura , y las operaciones de lectura son seguras para subprocesos , por lo que no es necesario que cada subproceso mantenga un bloqueo para llamar a este método método, necesitamos ajustar el tiempo de bloqueo .

En el caso de subprocesos múltiples , puede ocurrir un problema de puntero nulo.La razón del problema es que la JVM realizará operaciones de optimización y reordenación de instrucciones al crear instancias de objetos . Para resolver el problema de la excepción de puntero nulo causado por el modo de bloqueo de doble verificación, solo necesita usar volatilela palabra clave , volatileque puede garantizar la visibilidad y el orden .

/*
* 双重检查锁
* 添加 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 Método de clase interna estática

La instancia de este método es creada por la clase interna . Dado que la JVM no cargará la clase interna estática durante el proceso de carga de la clase externa, solo se cargarán las propiedades/métodos de la clase interna y sus propiedades estáticas se inicializarán. . Se garantiza que las propiedades estáticas se instanciarán una sola vez debido a que se staticmodifican , y el orden de instanciación está estrictamente garantizado .

El método de clase interna estática garantiza la seguridad de subprocesos sin bloqueos , y no hay impacto en el rendimiento ni desperdicio de espacio .

/*
* 静态内部类方式
* 第一次加载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
}

Cuarto, destruir el modo singleton

4.1 Destrucción de serialización

El resultado de ejecutar el código es falseque la serialización y la deserialización han roto el patrón de diseño de singleton.

El valor en el objeto es el mismo, pero la referencia del objeto es diferente, lo que equivale a dos objetos, lo que implica una copia profunda

/**
 * 静态内部类方式的单列模式
 */
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 Solución de destrucción de serialización

Para resolver la destrucción del patrón singleton por serialización y deserialización, necesitamos agregar readResolve()un método a la clase Singleton, llamarlo cuando esté deserializado y devolver el valor (objeto) de este método. El principio es que en la implementación subyacente del método readObject del objeto de flujo de entrada, si se encuentra que la entrada es un tipo de objeto, se llamará al método y se ejecutará automáticamente para determinar si hay readOrdinaryObjectun hasReadResolveMethodmétodo readResolveen la clase, y si es así, se ejecutará invokeReadResolveel método para obtener la instancia especialmente proporcionada en la clase, para resolver el problema de que la deserialización destruye el patrón singleton.

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

4.3 Destrucción de reflejos

El resultado de ejecutar el código es falseque la reflexión ha roto el patrón de diseño singleton.

/**
 * 静态内部类方式的单列模式
 */
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 Solución a la destrucción de la reflexión

Cuando se crea el constructor llamando al método de reflexión, se lanza una excepción directamente. No ejecute esta operación.

/**
 * 静态内部类方式的单列模式
 */
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;
    }
}

记录每一个学习瞬间

Supongo que te gusta

Origin blog.csdn.net/qq_51601665/article/details/131019523
Recomendado
Clasificación