Aprendizaje relacionado con la enumeración y el patrón de diseño de Java

Singleton hambriento

Hungry.java

/**
 * @Description 饿汉式单例
 */
public class Hungry {
    
    
    // 私有构造方法,不准其他类使用new创建
    private Hungry() {
    
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
    
    
        return hungry;
    }
}

Problema existente: si hay una gran cantidad de contenido de inicialización en la clase Hungry, se producirá una pérdida de espacio. Un ejemplo es el siguiente:

public class Hungry {
    
    

    // data1、data2、data3 此处是指存在大量的初始化内容,使用饿汉式单例的话会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];

    // 私有构造方法,不准其他类使用new创建
    private Hungry() {
    
    
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
    
    
        return hungry;
    }

}

Singleton perezoso

Patrón singleton simple de un solo hilo

Nota: Es seguro en el caso de un solo subproceso, pero pueden surgir problemas en el caso de varios subprocesos.

/**
 * @Description 懒汉式单例
 */
public class LazyMan {
    
    
	// 私有构造方法,不准其他类使用new创建
    private LazyMan() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
    private static LazyMan lazyMan;
    
    public static LazyMan getInstance() {
    
    
        if (lazyMan == null) {
    
    
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
                getInstance();
            }).start();
        }
    }

}

输出信息:(多线程情况运行处的结果不同)
Thread-0
Thread-2
Thread-1
Modo DCL singleton (modo singleton con modo de bloqueo de detección doble)

La instancia única creada por el código de un solo subproceso anterior getInstance () es segura para subprocesos en el caso de un solo subproceso, y se crearán varias instancias en el caso de subprocesos múltiples. Puede agregar un candado para resolver este problema.

el código se muestra a continuación:

/**
 * @Description 懒汉式单例模式-DCL单例模式(双重检测锁模式单例模式)
 */
public class LazyMan {
    
    
    // 私有构造方法,不准其他类使用new创建
    private LazyMan() {
    
    
    }

    // volatile保证可见性和禁止指令重排
    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance() {
    
    
        if(lazyMan == null){
    
    
            synchronized(LazyMan.class){
    
    
                if (lazyMan == null) {
    
    
                    lazyMan = new LazyMan();// 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }
}

Nota: Los objetos de instancia deben modificarse con volátiles para garantizar la visibilidad y prohibir el reordenamiento de instrucciones.

LazyMan lazyMan = new LazyMan (); Los
pasos se dividen de la siguiente manera:
1. Asignar espacio de memoria
2. Ejecutar el método de construcción e inicializar el objeto
3. Apuntar este objeto a este espacio

El reordenamiento de instrucciones se refiere a: el
orden de ejecución puede ser 123 o 132.
Si el hilo A ejecuta el paso 132, el hilo
B ingresa al método getInstance () y regresa a lazyMan directamente (es posible que lazyMan no haya completado la construcción en este momento)

Por lo tanto, el objeto de instancia aquí debe modificarse con volátil para garantizar la visibilidad y prohibir el reordenamiento de instrucciones.

El problema con este enfoque, la reflexión se puede utilizar para destruir el modo singleton

public static void main(String[] args) throws Exception {
    
    
    LazyMan lazyMan1 = getInstance();
    // 反射方式破坏单例模式
    Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    LazyMan lazyMan2 = constructor.newInstance();

    System.out.println(lazyMan1);
    System.out.println(lazyMan2);
}
输出结果:
com.xiangty.single.LazyMan@1540e19d
com.xiangty.single.LazyMan@677327b6

Puede utilizar un identificador privado para evitar los problemas de reflexión mencionados anteriormente, el código es el siguiente:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

/**
 * @Description 懒汉式单例模式-DCL单例模式(双重检测锁模式单例模式)
 *          添加一个如下的flag标识来避免反射。  (还是会存在问题,反射可以修改flag的值)
 */
public class LazyMan3 {
    
    

    private static boolean flag = false;

    // volatile保证可见性和禁止指令重排
    private volatile static LazyMan3 lazyMan;

    // 私有构造方法,不准其他类使用new创建
    private LazyMan3() {
    
    
        if(flag){
    
    
            throw new RuntimeException("对象已存在");
        } else {
    
    
            flag = true;
        }
    }

    public static LazyMan3 getInstance() {
    
    
        if (lazyMan == null) {
    
    
            synchronized (LazyMan3.class) {
    
    
                if (lazyMan == null) {
    
    
                    lazyMan = new LazyMan3(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }


    public static void main(String[] args) throws Exception {
    
    
//        verification();
        editProperties();
    }

    /**
     * 不修改flag的,验证方法,会出现异常
     *
     * @throws Exception
     */
    public static void verification() throws Exception {
    
    
//        LazyMan3 lazyMan1 = getInstance();
        Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazyMan3 lazyMan2 = constructor.newInstance();
//        LazyMan3 lazyMan1 = constructor.newInstance();
        LazyMan3 lazyMan1 = getInstance();

        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }

    /**
     * 修改flag属性值的破坏方法,不会出现异常,会声明两对象
     *
     * @throws Exception
     */
    public static void editProperties() throws Exception {
    
    
        Field field = LazyMan3.class.getDeclaredField("flag");
        field.setAccessible(true);

        Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazyMan3 lazyMan2 = constructor.newInstance();

        // flag重设值
        field.set(lazyMan2, false);

        LazyMan3 lazyMan1 = constructor.newInstance();
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }

}
No puedo usar la reflexión para destruir la enumeración

Las clases ordinarias pueden destruir el patrón singleton mediante la reflexión. Verifique el código fuente de Constructor.java y descubrió que la reflexión no se puede usar para destruir enumeraciones.

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
    
    
        if (!override) {
    
    
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
    
    
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        // 不能使用反射破坏枚举
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
    
    
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

Definir una clase de enumeración EnumSingle.java

public enum EnumSingle {
    
    
    INSTANCE;

    public EnumSingle getInstance() {
    
    
        return INSTANCE;
    }
}

Prueba la clase uno usando la reflexión

public class NoParametersTestEnumSingle{
    
    
    /**
     * 无参的构造方法反射
     * 异常信息:Exception in thread "main" java.lang.NoSuchMethodException: com.xiangty.single.EnumSingle.<init>()
     */
    public static void main(String[] args) throws Exception {
    
    
        EnumSingle enumSingle1 = EnumSingle.INSTANCE;
        // 没有无参的构造方法
        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        EnumSingle enumSingle2 = constructor.newInstance();

        System.out.println(enumSingle1);
        System.out.println(enumSingle2);
    }
}

Usando el método de construcción sin parámetros en la reflexión, el mensaje de excepción solicitó: Excepción en el hilo "principal" java.lang.NoSuchMethodException: com.xiangty.single.EnumSingle. (), No hay un método de construcción sin parámetros. Esta información no es la "No se pueden crear objetos de enumeración de forma reflexiva" que se menciona en el código fuente.

Vea el archivo compilado de EnumSingle.class en IDEA de la siguiente manera:

public enum EnumSingle {
    
    
    INSTANCE;

    private EnumSingle() {
    
    
    }

    public EnumSingle getInstance() {
    
    
        return INSTANCE;
    }
}

Existe un método de construcción sin parámetros en el archivo compilado, entonces, ¿por qué obtenemos una excepción cuando usamos la construcción sin parámetros de reflexión? Aquí debe tomar prestada la herramienta de descompilación jad.exe para ver cómo se ve la implementación subyacente de EnumSingle.java.

Inserte la descripción de la imagen aquí

Use el software jad.exe para descompilar EnumSingle.class y luego puede obtener el contenido de EnumSingle.java de la siguiente manera:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

public final class EnumSingle extends Enum
{
    
    

    public static EnumSingle[] values()
    {
    
    
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
    
    
        return (EnumSingle)Enum.valueOf(EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
    
    
        super(s, i);
    }

    public EnumSingle getInstance()
    {
    
    
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
    
    
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
    
    
            INSTANCE
        });
    }
}

Encontramos que la clase de enumeración definida no tiene un método de construcción sin parámetros, pero hay un método de construcción parametrizado. El método es el siguiente.

private EnumSingle(String s, int i)
{
    super(s, i);
}

Escriba un método con el método de reflexión de construcción de parámetros, el código es el siguiente:

public class HavaParametersTest{
    
    
/**
     * 有参的构造方法反射
     * 异常信息:Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
     */
    public static void main(String[] args) throws Exception {
    
    
        EnumSingle enumSingle1 = EnumSingle.INSTANCE;
        // 没有无参的构造方法
        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        EnumSingle enumSingle2 = constructor.newInstance();

        System.out.println(enumSingle1);
        System.out.println(enumSingle2);
    }
}

Al utilizar el método de construcción de reflexión con parámetros, puede obtener la misma información de excepción que el código fuente: Excepción en el hilo "main" java.lang.IllegalArgumentException: No se pueden crear objetos de enumeración de forma reflexiva.

Supongo que te gusta

Origin blog.csdn.net/qq_33369215/article/details/105876458
Recomendado
Clasificación