Explore el patrón singleton en profundidad

Recientemente, estudié el modo singleton. Después de ver el maestro bilibili up "Crazy God Says Java", descubrí que la mayoría de los blogs tienen un enlace muy interesante. Es una pena no compartirlo. El video original https: // www.bilibili.com/video/BV1K54y197iS

1. Comprender el singleton

这个部分小部分我相信很多博客都讲的很好,我就尽量精简了
  1. Nota:
  • La clase Singleton solo puede tener una instancia
  • Esta instancia la crea usted mismo
  • Esta instancia debe proporcionarse al mundo exterior.
  1. Clave: privatización del constructor
  2. Método de creación:
  • Hambriento
  • Perezoso

Resumen: creo que el método de creación se puede enraizar de dos maneras, una es el estilo chino hambriento, lo creo cuando se carga la clase; también hay un estilo chino perezoso, solo cuando necesito crear

2. Ideas y realización

[La realización más básica del modelo del hombre hambriento]

Se creó cuando se cargó la clase. En este modo, los subprocesos son seguros. Diferentes subprocesos obtienen la misma instancia. Sin embargo, también hay un problema de espacio desperdiciado. Se carga cuando no lo necesito .

//饿汉模式
 public class HungerSingle {
    private static HungerSingle single = new HungerSingle();
    //构造器私有,外界不能通过构造方法new对象,保证唯一
    private HungerSingle() {
    }
    //提供外界获得该单例的方法,注意方法只能是static方法,因为没有类实例
    public static HungerSingle getInstance(){
        return single;
    }
}

[La realización más básica del modo perezoso]

Para resolver el problema anterior de pérdida de espacio, el modo diferido funcionará en este momento. Crearé esta instancia cuando me necesites.

//懒汉模式
public class LazySingle {
    private static LazySingle single;
    //构造器私有化,禁止外部new生成对象
    private LazySingle(){
    }
    //外界获得该单例的方法
    public static LazySingle getInstance(){
        if(single == null){
            single = new LazySingle();
        }
        return single;
    }
 }

Valorar predecesores entusiastas :. "Como se escribe un solo caso en nuestra empresa son para ser expulsado",
mientras era estudiante, después de ser expulsado con el estado de ánimo, seguir aprendiendo a ir
en el modo vago original, hilo Singleton No es seguro

¿Cómo probarlo? Como sigue

[Probar hilo de modo diferido no es seguro]

//1、构造器
private LazySingle(){
    System.out.println(Thread.currentThread().getName());
}

//创建十个线程
for (int i = 0; i < 10; i++) {
    new Thread(()->{
         Singleton2.getInstance();
    }).start();
}

En este punto, encontrará que se llama al constructor más de una vez, lo que indica que el singleton esperado no está implementado

Por lo general, resolvemos el método inseguro del hilo: no es inseguro, es fácil de manejar, bloquear

【Doble bloqueo de detección / DCL】

public class DCLSingle {
    private static DCLSingle single;
    private DCLSingle(){
    }
    public static DCLSingle getInstance(){
        //第一次判断,没有这个对象才加锁
        if(single == null){
            //哪个需要保护,就锁哪个
            synchronized (DCLSingle.class){
                //第二次判断,没有就实例化
                if(single == null){
                    single = new DCLSingle();
                }
            }
        }
        return single;
    }

}

Después de una cuidadosa comparación con el código de otras personas, descubrí que me falta una palabra clave volátil. ¿Qué es esto?
Pregunta si no entiendes.

[Volátil]
Para evitar la reorganización de instrucciones

//上述代码声明上面加上volatile关键字
 private volatile static DCLSingle single;

¿Qué es volátil?

Cita del blog de otros
https://www.cnblogs.com/YLsY/p/11295732.html

Agregar volátil es para la aparición de lecturas sucias para garantizar la atomicidad de la operación

1、原子性操作:不可再分割的操作
例如:single = new DCLSingle();
其实就是两步操作:
①new DCLSingle();//开辟堆内存
②singl指向对内存

2、脏读
Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。
线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。
并且每个线程不能访问其他线程的工作内存。
变量的值何时从线程的工作内存写回主存,无法确定。

3、指令重排
single = new DCLSingle();
先执行②
后执行①
//先指向堆内存,还未完成构造


【模拟情况】
①线程1执行,在自己的工作内存定义引用,先指向堆内存,还未构造完成
②此时线程2执行,它进行判断,引用已经指向了内存,所以线程2,认为构造完成,实际还未构造完成

Hay otra forma en que casi se me olvida decirlo, que también es la forma recomendada en el tutorial para novatos

[Caso único de implementación de clase interna estática]

public class Singleton {
    private Singleton(){}
    private static class SingleIN{
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton getInstance(){
        return SingleIN.INSTANCE;
    }
}

Encontrará que es muy similar al estilo chino hambriento ordinario mencionado anteriormente. También lo clasifico como un estilo chino hambriento porque también es directamente nuevo Singleton, pero tiene el efecto de una carga lenta, y este método es la clase Singleton. Después de cargarse, la instancia puede no inicializarse. Debido a que la clase SingletonHolder no se usa activamente, solo cuando se llama explícitamente al método getInstance, la clase SingletonHolder se cargará explícitamente para crear una instancia de la instancia.

[Recomendado] Se recomienda usar una implementación de clase interna estática


## 3. Cómo romper el caso único (el contenido que la mayoría de los otros blogs no tienen). Gracias por la estación b aquí [Mad God dijo java]

Es hora de actuar delante del entrevistador.

El lenguaje Java se da cuenta de la dinámica reflexión del alma, diciendo: No hay nada que no pueda cambiar, depende de cómo opere.

[Caso único de daño por reflexión]

public class DCLSingle {
    private static DCLSingle single;
    private DCLSingle(){
    }
    public static DCLSingle getInstance(){
        //第一次判断,没有这个对象才加锁
        if(single == null){
            //哪个需要保护,就锁哪个
            synchronized (DCLSingle.class){
                //第二次判断,没有就实例化
                if(single == null){
                    single = new DCLSingle();
                }
            }
        }
        return single;
    }
    
    //通过反射破化单例
    public static void main(String[] args) throws Exception {
        LazySingle single = LazySingle.getInstance();
        Constructor<LazySingle> constructor = LazySingle.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazySingle single1 = constructor.newInstance();
        System.out.println(single == single1);//false
    }

}

Obtenga el constructor de la clase singleton y luego cree un objeto a través del método newInstance, que obviamente rompe el singleton

[Mejore el código para evitar que lo rompa]

Dado que rompió el constructor esta vez, agrego un método al constructor, si ha creado una instancia, entonces arroje una excepción

private LazySingle(){
    synchronized(LazySingle.class){
        if(single!=null){
            throw new RuntimeException("破坏失败");
        }
    }
}

Pero hay un problema con esto. El juicio aquí es si DCLSingle single estático privado tiene valor. Si no creamos objetos a través del método getInstance (), es así

public static void main(String[] args) throws Exception {
 //   LazySingle single = LazySingle.getInstance();
    Constructor<LazySingle> constructor = LazySingle.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    
    //注意:这里的对象不是单例类中里面属性的那个对象
    LazySingle single = constructor.newInstance();
    LazySingle single1 = constructor.newInstance();
    System.out.println(single == single1);//false
}

Aquí no se lanza ninguna excepción, pero el singleton se destruye nuevamente

[Continuar mejorando el código y evitar que se rompa] Es
simplemente quererse y matarse mutuamente. Podemos usar el principio de la farola roja para evitar la rotura y
mejorar el método de construcción.

//加个标志
private static String sign = "password";
private LazySingle(){
    synchronized(LazySingle.class){
        if(single!=null || !"password".equals(sign)){
            throw new RuntimeException("破坏失败");
        }else{
            sign = "no";
        }
    }
    
}

En este momento, pasa la prueba de contenido en el método main () anterior y descubre que arrojará una excepción nuevamente. Sin embargo, podemos obtener el método de construcción a través de la reflexión, luego también podemos obtener las propiedades y valores del objeto a través de la reflexión.

【Destruye de nuevo】

public static void main(String[] args) throws Exception {
    Constructor<LazySingle> constructor = LazySingle.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Field field = LazySingle.class.getDeclaredField("sign");
    //此处省略通过反射获取该属性的类型和方法....
    LazySingle single1 = constructor.newInstance();
    //重新变回原标志位
    field.set("sign","password");
    LazySingle single2 = constructor.newInstance();
    System.out.println(single2 == single1);//false
}

Roto de nuevo

[Mejorar de nuevo]


Dirigimos nuestra atención a la enumeración. Después de jdk1.5, se produce la enumeración. El
uso de la enumeración no solo puede evitar problemas de sincronización de subprocesos múltiples, sino que también admite automáticamente el mecanismo de serialización para evitar que la deserialización vuelva a crear nuevos objetos.Absolutamente prevenir múltiples instancias(Término oficial para el tutorial de novato)

public enum Singleton {  
    INSTANCE;  
    public Singleton getInstance() {  
        return INSTANCE
    }  
}

[¿Puede la reflexión romper el singleton de la enumeración?

  1. Primero debemos entender qué es la enumeración y cómo se implementa en la parte inferior
  2. Encontraremos que la enumeración en sí es un
  3. A través de la herramienta de descompilación, vea el método de enumeración de construcción subyacente
  4. Obtener el constructor por reflexión
  5. Repita la prueba anterior.

Finalmente podemos encontrar singletons donde la reflexión no puede romper las enumeraciones

Esta implementación no ha sido ampliamente adoptada, pero es la mejor manera de lograr el patrón singleton. Es más conciso y admite automáticamente el mecanismo de serialización, evitando absolutamente múltiples instancias. (Tutorial oficial para novatos)

[Resumen] Es muy difícil

Supongo que te gusta

Origin www.cnblogs.com/yxm2020/p/12723418.html
Recomendado
Clasificación