Patrón de diseño_Patrón creativo-《Patrón Singleton》

Patrón de diseño_Patrón creativo-《Patrón Singleton》

Las notas están organizadas a partir de la explicación detallada de los patrones de diseño de Java por programadores de caballos oscuros, 23 patrones de diseño de Java (diagrama + análisis de código fuente del marco + combate real)

El enfoque principal del patrón de creación es "¿cómo crear objetos?", y su característica principal es "separación de la creación y el uso de objetos".

Esto puede reducir el acoplamiento del sistema y los usuarios no necesitan prestar atención a los detalles de la creación de objetos.

Los patrones de creación se dividen en:

  • patrón único
  • patrón de método de fábrica
  • patrón de fábrica abstracto
  • patrón de prototipo
  • modo constructor

patrón de diseño único

Singleton Pattern (Patrón Singleton) es uno de los patrones de diseño más simples en Java. Este tipo de patrón de diseño pertenece 创建型模式y proporciona una forma óptima de crear objetos.

Este patrón implica una sola clase que es responsable de crear sus propios objetos mientras se asegura de que solo se cree un único objeto. Esta clase proporciona una forma de acceder a su único objeto, directamente, sin instanciar un objeto de esta clase.

La estructura del patrón singleton

Las funciones principales del modo singleton son las siguientes:

  • Clase Singleton: una clase en la que solo se puede crear una instancia
  • Clase de acceso: use la clase singleton

Implementación del patrón singleton

Hay dos tipos de patrones de diseño singleton:

  • Estilo chino hambriento : la carga de clase hará que se cree el objeto de instancia única
  • Estilo perezoso : la carga de clases no hará que se cree el objeto de instancia única, pero se creará cuando el objeto se use por primera vez

Estilo chino hambriento

  • Estilo chino hambriento: modo 1 (modo variable estático)

    /**
     * 饿汉式
     * 静态变量创建类的对象
     */
    public class Singleton {
          
          
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 在成员位置直接创建该类的对象
        private static Singleton instance = new Singleton();
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return instance;
        }
    }
    

    ilustrar:

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

  • Estilo chino hambriento: método 2 (método de bloque de código estático)

    /**
     * 饿汉式
     * 在静态代码块中创建该类对象
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance; // null
    
        // 在静态代码块中进行赋值
        static {
          
          
            instance = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return instance;
        }
    }
    

    ilustrar:

    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 1 del estilo chino hambriento. Por supuesto, este método también tiene el problema del desperdicio de memoria .

estilo perezoso

  • Estilo perezoso - forma 1 (subproceso inseguro)

    /**
     * 懒汉式
     * 线程不安全
     */
    public class Singleton {
          
          
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance; // null
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            // 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
            // 如果没有,创建一个并返回,如果有,直接返回
            if (instance == null) {
          
          
                // 多线程环境下:
                // 线程1先抢占到cpu的执行权,但还未执行下面这行代码,此时线程2获取到cpu的执行权,也会进入到该if判断里面,
                // 导致它们都会进行一次创建对象的操作,此时的对象非单例了。
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    ilustrar:

    Del código anterior, podemos ver que este método declara una variable estática de tipo Singleton en la posición del miembro y no realiza la asignación de objetos, entonces, ¿cuándo se asigna? El objeto de la clase Singleton se crea cuando se llama al método getInstance() para obtener el objeto de la clase Singleton, logrando así el efecto de carga diferida. Sin embargo, si se trata de un entorno de subprocesos múltiples, habrá problemas de seguridad de subprocesos.

  • Estilo perezoso - forma 2 (seguridad de subprocesos)

    /**
     * 懒汉式
     * 线程安全
     */
    public class Singleton {
          
          
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance;
    
        // 对外提供静态方法获取该对象 方法上加了synchronized独占锁
        public static synchronized Singleton getInstance() {
          
          
    		// 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
            if (instance == null) {
          
          
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    ilustrar:

    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, la palabra clave se agrega al método getInstance() synchronized, lo que hace que el efecto de ejecución de este método sea particularmente bajo . Del código anterior, podemos ver que el problema de seguridad de subprocesos solo ocurre cuando se inicializa la instancia y no existe una vez que se completa la inicialización.

  • Lazy-way 3 (cerraduras doblemente comprobadas)

    Analicemos nuevamente el problema de bloquear en modo perezoso. Para getInstance()los métodos, la mayoría de las operaciones son operaciones de lectura, y las operaciones de lectura son seguras para subprocesos, por lo que no necesitamos que cada subproceso mantenga un bloqueo para llamar al método. Necesitamos ajustar el tiempo de bloqueo. Esto también resultó en un nuevo modo de implementación: el modo de bloqueo de doble verificación.

    /**
     * 双重检查方式
     */
    public class Singleton {
          
           
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance;
    
       	// 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    		// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
            if (instance == null) {
          
          
                synchronized (Singleton.class) {
          
          
                    // 双重判断,抢到锁之后再次判断是否为null
                    if (instance == null) {
          
          
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    El modo de bloqueo de doble verificación es un muy buen modo de implementación de singleton, que resuelve los problemas de singleton, rendimiento y seguridad de subprocesos. El modo de bloqueo de doble verificación anterior parece perfecto, pero en realidad hay problemas. En el caso de subprocesos múltiples , puede haber 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 .

    /**
     * 双重检查方式
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量 使用volatile修饰
        private static volatile Singleton instance;
    
       	// 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    		// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
            if (instance == null) {
          
          
                synchronized (Singleton.class) {
          
          
                    // 抢到锁之后再次判断是否为空
                    if (instance == null) {
          
          
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    resumen:

    El modo de bloqueo de verificación doble después de agregar volatilela palabra clave es un mejor modo de implementación singleton, que puede garantizar la seguridad de subprocesos sin problemas de rendimiento en el caso de subprocesos múltiples.

  • Estilo perezoso - método 4 (método de clase interna estática)

    En el modo singleton de clase interna estática, la instancia 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 cuando se llamen las propiedades/métodos de la clase interna, lo hará. ser cargado e inicializado propiedades estáticas. Se garantiza que las propiedades estáticas staticse instanciarán solo una vez debido a que se modifican, y el orden de instanciación está estrictamente garantizado.

    /**
     * 静态内部类方式
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 定义一个静态内部类
        private static class SingletonHolder {
          
          
            // 在内部类中声明并初始化外部类的对象
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return SingletonHolder.INSTANCE;
        }
    }
    

    ilustrar:

    La INSTANCIA no se inicializará cuando la clase Singleton se cargue por primera vez, solo se llama a getInstance por primera vez, y la máquina virtual carga el SingletonHolder e inicializa la INSTANCIA, lo que no solo garantiza la seguridad del subproceso, sino que también garantiza la singularidad de la clase Singleton.

    resumen:

    El patrón singleton de clase interna estática es un excelente patrón singleton y es un patrón singleton de uso común en proyectos de código abierto. Sin agregar ningún bloqueo, se garantiza la seguridad con subprocesos múltiples y no hay impacto en el rendimiento ni desperdicio de espacio.

  • método de enumeración

    La clase de enumeración implementa el modo singleton es un modo de implementación singleton altamente recomendado, porque el tipo de enumeración es seguro para subprocesos y solo se cargará una vez. El diseñador hace pleno uso de esta característica de la enumeración para implementar el modo singleton. La enumeración El El método de escritura es muy simple, y el tipo de enumeración es el único modo de implementación de singleton que no se puede destruir en la implementación de singleton utilizada.

    /**
     * 枚举方式
     */
    public enum Singleton {
          
          
        INSTANCE;
    }
    

    ilustrar:

    El método de enumeración pertenece al método chino hambriento .

Problemas con el patrón singleton

demostración de problemas

Destruyendo el patrón singleton:

Hacer que la clase singleton (Singleton) definida anteriormente pueda crear varios objetos, excepto el método de enumeración. Hay dos formas, serialización y reflexión.

  • serialización deserialización

    Singleton类

    /**
     * 静态内部类方式
     * 实现序列化接口
     */
    public class Singleton implements Serializable {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        private static class SingletonHolder {
          
          
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return SingletonHolder.INSTANCE;
        }
    }
    

    Test类

    /**
     * 测试使用序列化和反序列化破坏单例模式
     */
    public class Test {
          
          
        public static void main(String[] args) throws Exception {
          
          
            // 往文件中写对象
            //writeObject2File();
            
            // 从文件中读取对象
            Singleton s1 = readObjectFromFile();
            Singleton s2 = readObjectFromFile();
    
            // 判断两个反序列化后的对象是否是同一个对象
            System.out.println(s1 == s2);
        }
    
        // 从文件读取数据(对象)
        private static Singleton readObjectFromFile() throws Exception {
          
          
            // 1.创建对象输入流对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            // 2.读取Singleton对象
            Singleton instance = (Singleton) ois.readObject();
    		// 3.释放资源
            ois.close();
            // 4.返回读取到的对象
            return instance;
        }
    
        // 向文件中写数据(对象)
        public static void writeObject2File() throws Exception {
          
          
            // 1.获取Singleton类的对象
            Singleton instance = Singleton.getInstance();
            // 2.创建对象输出流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            // 3.将instance对象写出到文件中
            oos.writeObject(instance);
            // 4.释放资源
            oos.close();
        }
    }
    

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

  • reflexión

    Singleton类

    /**
     * 双重检查方式
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
        
        private static volatile Singleton instance;
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    
            if (instance != null) {
          
          
                return instance;
            }
    
            synchronized (Singleton.class) {
          
          
                if (instance != null) {
          
          
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    Test类

    /**
     * 测试使用反射破坏单例模式
     */
    public class Test {
          
          
        public static void main(String[] args) throws Exception {
          
          
            // 1.获取Singleton类的字节码对象
            Class clazz = Singleton.class;
            // 2.获取Singleton类的私有无参构造方法对象
            Constructor constructor = clazz.getDeclaredConstructor();
            // 3.取消访问检查
            constructor.setAccessible(true);
    
            // 4.1 创建Singleton类的对象s1
            Singleton s1 = (Singleton) constructor.newInstance();
            // 4.2 创建Singleton类的对象s2
            Singleton s2 = (Singleton) constructor.newInstance();
    
            // 5.判断通过反射创建的两个Singleton对象是否是同一个对象
            System.out.println(s1 == s2);
        }
    }
    

    El resultado de ejecutar el código anterior falsemuestra que la forma de reflexión ha roto el patrón de diseño de singleton.

Nota: Estos dos problemas no ocurrirán con el método de enumeración.

resolución de problemas

  • La solución a la destrucción del modo singleton por serialización y deserialización

    Agregue el método en la clase Singleton readResolve(), que será llamado por reflexión durante la deserialización. Si se define este método, se devolverá el valor de este método. Si no se define, se devolverá el objeto recién creado.

    Singleton类

    /**
     * 静态内部类方式
     * 实现序列化接口
     */
    public class Singleton implements Serializable {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        private static class SingletonHolder {
          
          
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return SingletonHolder.INSTANCE;
        }
        
        /**
         * 下面是为了解决序列化反序列化破解单例模式
         * 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
         */
        private Object readResolve() {
          
          
            return SingletonHolder.INSTANCE;
        }
    }
    

    Análisis de código fuente

    ObjectInputStream类

        public final Object readObject()
            throws IOException, ClassNotFoundException
        {
          
          
            ...
            // if nested read, passHandle contains handle of enclosing object
            int outerHandle = passHandle;
            try {
          
          
                Object obj = readObject0(false);//重点查看readObject0方法
            ...
        }
    
        private Object readObject0(boolean unshared) throws IOException {
          
          
            ...
            try {
          
          
                switch (tc) {
          
          
                    ...
                    case TC_OBJECT:
                        return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
                    ...
                }
            } finally {
          
          
                depth--;
                bin.setBlockDataMode(oldMode);
            }    
        }
    
        private Object readOrdinaryObject(boolean unshared) 
            throws IOException
        {
          
          
            ...
            // isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
            obj = desc.isInstantiable() ? desc.newInstance() : null; 
            ...
            // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
            if (obj != null && 
                handles.lookupException(passHandle) == null && 
                desc.hasReadResolveMethod()) 
            {
          
          
                // 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
                // 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
                Object rep = desc.invokeReadResolve(obj);
                ...
            }
            return obj;
        }
    
  • La solución para descifrar singletons por reflexión

    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          
            /*
             * 反射破解单例模式需要添加的代码
             */
            if (instance != null) {
          
          
                throw new RuntimeException("不能创建多个对象");
            }
        }
        
        private static volatile Singleton instance;
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    
            if (instance != null) {
          
          
                return instance;
            }
    
            synchronized (Singleton.class) {
          
          
                if (instance != null) {
          
          
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    ilustrar:

    De esta manera es más fácil de entender. Cuando se crea el constructor llamando al método de reflexión, se lanza una excepción directamente. No ejecute esta operación.

Análisis de código fuente de JDK: clase de tiempo de ejecución

La clase Runtime es el patrón de diseño singleton utilizado.

  • Vea qué patrón singleton se usa a través del código fuente

    public class Runtime {
          
          
        // 饿汉式初始化
        private static Runtime currentRuntime = new Runtime();
    
        /**
         * Returns the runtime object associated with the current Java application.
         * Most of the methods of class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> object associated with the current
         *          Java application.
         */
        public static Runtime getRuntime() {
          
          
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {
          
          }
        ...
    }
    

    Se puede ver en el código fuente anterior que la clase Runtime usa el método Hungry Chinese (atributo estático) para implementar el modo singleton.

  • Uso de métodos en la clase Runtime

    public class RuntimeDemo {
          
          
        public static void main(String[] args) throws IOException {
          
          
            // 获取Runtime类对象
            Runtime runtime = Runtime.getRuntime();
    
    		// 调用runtime的方法
            // 返回 Java 虚拟机中的内存总量
            //System.out.println(runtime.totalMemory());
            // 返回 Java 虚拟机试图使用的最大内存量
            //System.out.println(runtime.maxMemory());
    
            // exec方法,创建一个新的进程执行指定的字符串命令,返回进程对象
            Process process = runtime.exec("ipconfig");
            // 获取命令执行后的结果,通过输入流获取
            InputStream inputStream = process.getInputStream();
            byte[] arr = new byte[1024 * 1024* 100];
            // 读取数组 存到arr字节数组中
            int b = inputStream.read(arr); // 返回读到的字节的个数
            // 将字节数组转换为字符串输出到控制台
            System.out.println(new String(arr, 0, b, "gbk"));
        }
    }
    

    Salida de la consola:

    imagen-20230102203500745
    Es el mismo efecto que se ve al ejecutar el comando en nuestra ventana de línea de comando local.

Supongo que te gusta

Origin blog.csdn.net/weixin_53407527/article/details/128620123
Recomendado
Clasificación