Una introducción perezosa al patrón singleton

La ortografía más básica del hombre perezoso.

El código que se muestra al final contiene procesos y anotaciones detalladas, que pueden entenderse junto con el código final.

ortografía

Echemos un vistazo a la escritura más básica al estilo del hombre perezoso primero, sin considerar ningún problema de seguridad.

package singleton;

public class LazyMan {

    private LazyMan() {
        System.out.println(Thread.currentThread().getName()+"ok");
    }
    
    private static LazyMan lazyMan = null;
    
    public static LazyMan getInstance() {
        if(lazyMan==null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

}
复制代码

prueba

Luego escribimos un código de prueba para ver si cumple con los requisitos del singleton

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

resultado

Echemos un vistazo a la salida.

imagen.png

En la imagen, podemos ver claramente que esto no sigue los requisitos del patrón singleton, por lo que debemos resolver el problema de seguridad del subproceso del singleton.

Modo de detección doble para resolver el problema de seguridad del subproceso perezoso

código

Aquí está el código mejorado:

package singleton;

public class LazyMan {

    private LazyMan() {
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private static LazyMan lazyMan = null;

    public static LazyMan getInstance() {
        if(lazyMan==null) {
            synchronized (LazyMan.class) {
                if(lazyMan==null) {
                    lazyMan = new LazyMan();
                }
            }
        }

        return lazyMan;
    }

}
复制代码

prueba

vamos a probarlo

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

resultado

imagen.png

En este punto, resolvimos el problema de seguridad de subprocesos, por lo que parece que el problema está básicamente resuelto. Pero cuando estábamos entrevistando, la mayoría de la gente podía escribir de esta manera, así que necesitábamos algunos trucos.

Las operaciones de agitación de algunos perezosos

Rompiendo el patrón de bloqueo de doble verificación

Primero, rompamos el modo de bloqueo de doble detección anterior, y la siguiente es una demostración del código roto

public static void main(String[] args) throws Exception {
    LazyMan test1 = LazyMan.getInstance();
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan test2 = declaredConstructor.newInstance();
    System.out.println(test1);
    System.out.println(test2);

}
复制代码

Echemos un vistazo a los resultados.

imagen.png

Vemos que el resultado en este momento no se ajusta al patrón singleton.

Resolver operaciones que rompen bloqueos de detección doble

Solo necesitamos un paso para resolver este problema, que es lanzar excepciones manualmente. La implementación es la siguiente:

private LazyMan() {
    if(lazyMan!=null) {
        throw new RuntimeException("不可以通过反射来破坏哦~");
    }
}
复制代码

Echemos un vistazo a los resultados en este punto:

imagen.png

Podemos ver que el problema se resuelve con una sola reflexión en este momento. Pero, ¿por qué escribir una reflexión aquí? Sin un amigo preguntando, si ambos obtuvimos instancias a través de la reflexión, ¿volverá a pasar algo? La respuesta es sí. A continuación continuamos profundizando en este tema.

Continuar rompiendo operaciones que han resuelto rompiendo bloqueos doblemente verificados

código directamente

public static void main(String[] args) throws Exception {
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan test1 = declaredConstructor.newInstance();
    LazyMan test2 = declaredConstructor.newInstance();
    System.out.println(test1);
    System.out.println(test2);

}
复制代码

Echemos un vistazo a los resultados.

imagen.png

Ah, realmente fue destruido (^-^)V. Pero el diablo mide un pie de altura y el Tao mide un pie de altura, por lo que debe haber una solución. Veamos la solución a continuación.

Resolver la operación de obtener una instancia a través de dos reflexiones

Sin más preámbulos, veamos directamente el código.

private static boolean del = false;

private LazyMan() {
    synchronized (LazyMan.class){
        if(del==false) {
            del = true;
        } else  {
            throw new RuntimeException("不可以通过反射来破坏哦~");
        }
    }

}
复制代码

Echemos un vistazo a los resultados.

imagen.png

Resolvimos este problema a juzgar por la creación de la bandera del. Pero sabemos que la reflexión es omnipotente, por lo que esta solución aún se puede romper.

destruir la bandera

Veamos directamente el código.

public static void main(String[] args) throws Exception {
    Field del = LazyMan.class.getDeclaredField("del");
    del.setAccessible(true);
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan test1 = declaredConstructor.newInstance();
    del.set(test1,false);
    LazyMan test2 = declaredConstructor.newInstance();
    System.out.println(test1);
    System.out.println(test2);

}
复制代码

Echemos un vistazo a los resultados.

imagen.png

Podemos ver que el patrón singleton se rompe de nuevo. Ayuda, el problema no se ha resuelto hasta ahora, ¿no es esto una muñeca de anidación? Hablemos de cómo resolver este tipo de problema.

solución final

Sabemos que la enumeración no puede obtenerse por reflexión, por lo que solo podemos evitar la destrucción de la reflexión enumerando clases. Chicos, ¿habéis pensado en ello? Bueno, esta es mi introducción perezosa al patrón singleton, y puede dar sugerencias.

El código final es el siguiente (con anotaciones detalladas)

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

//懒汉式单例
//道高一尺,魔高一丈,还是没能解决反射造成的安全问题,想解决得看枚举
public class LazyMan {

   //这个的目的是解决俩次都是调用反射创建对象不安全的问题
   private static boolean delTwoFanShe = false;

   private LazyMan(){

//        System.out.println(Thread.currentThread().getName()+"ok");

       synchronized (LazyMan.class) {

           if(delTwoFanShe == false) {
               delTwoFanShe = true;
           } else {
               throw new RuntimeException("不要试图使用反射破坏异常");
           }

//            if(lazyMan!=null) {
//                //此方式是解决创建实例时一个用的getInstance(),另一个用的反射得到的,但是解决不了俩次都是通过反射获得的情况
//                throw new RuntimeException("不要试图使用反射破坏异常");
//            }
       }

   }

   private volatile static LazyMan lazyMan = null;

   //双重检测锁模式的懒汉式模式,简称DCL懒汉式
   public static LazyMan getInstance() {
       //上锁
       if(lazyMan==null) {
           synchronized (LazyMan.class) {
               if(lazyMan==null) {
                   lazyMan = new LazyMan();//不是一个原子性操作
                   /**
                    * 1.分配内存空间
                    * 2.执行构造方法,初始化对象
                    * 3.把这个对象指向这个空间
                    * 假如不是按照123步骤执行的话,就是发生了指令重排,那么lazyMan就没有完成初始化操作
                    */
               }
           }
       }

       return lazyMan; //如果不是按顺序执行123操作的话,此时lazyMan还没有完成构造,所以必须加上volatile来避免这种问题的发生
   }


   //我们写main函数来破坏,不断增强懒汉式的安全性
   public static void main(String[] args) throws Exception {

//        for(int i=0;i<10;i++) {
//            new Thread(()->{
//                LazyMan.getInstance();
//            }).start();
//        }

//        LazyMan instance = LazyMan.getInstance();  //这是一般的获取方式

       //我们得到自己设置的隐藏值,进行破坏
       Field delTwoFanShe = LazyMan.class.getDeclaredField("delTwoFanShe");
       delTwoFanShe.setAccessible(true);

       Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);

       declaredConstructor.setAccessible(true);

       LazyMan lazyMan = declaredConstructor.newInstance();//通过反射得到实例对象

       //我们修改第一次的实例,来将值变成false,进行破坏
       delTwoFanShe.set(lazyMan,false);

       LazyMan lazyMan1 = declaredConstructor.newInstance();//俩次实例对象都由反射来实现,造成破坏

       System.out.println(lazyMan);
       System.out.println(lazyMan1);

   }

}
复制代码

Supongo que te gusta

Origin juejin.im/post/7086738661227626503
Recomendado
Clasificación