Modo singleton de patrones de diseño comunes

concepto

Singleton Pattern (Singleton Pattern) es uno de los patrones de diseño más simples de Java. Este tipo de patrón de diseño es un patrón de creación, que proporciona la mejor manera de crear objetos.
Este patrón involucra una sola clase que es responsable de crear sus propios objetos mientras se asegura de que solo se cree un objeto. Esta clase proporciona una forma de acceder a su objeto único, al que se puede acceder directamente sin instanciar objetos de esta clase.

Escritura común

Singleton hambriento

Crea un singleton cuando la clase singleton se carga por primera vez

public class HungerSingleton{
    
    
 	private static final HungerSingleton instance =new HungerSingleton();
    private HungerSingleton(){
    
    }
    public static HungerSingleton getInstance(){
    
    
        return instance;
    }
}

Desventaja

  • Desperdicio de espacio de memoria, no apto para escenarios donde se crea una gran cantidad de objetos singleton

Singleton perezoso

Para resolver el problema del desperdicio de memoria que puede causar el singleton hambriento, se propone el singleton perezoso. La característica del singleton perezoso es que el objeto singleton se inicializará solo cuando se use.

Subproceso inseguro

public class LazySingleton{
    
    
    private static LazySingleton instance =null;
    private LazySingleton(){
    
    }
    public static LazySingleton getInstance(){
    
    
        if(instance==null){
    
    
            return new LazySingleton();
        }
        return instance;
    }
}

Desventaja

  • Subproceso inseguro

A salvo de amenazas

public class LazySingleton{
    
    
    private static LazySingleton instance =null;
    private LazySingleton(){
    
    }
    public static synchronized LazySingleton getInstance(){
    
    
        if(instance==null){
    
    
            return new LazySingleton();
        }
        return instance;
    }
}

Desventaja

  • Debe bloquear sincronizado para garantizar el singleton, pero el bloqueo afectará la eficiencia.

Cerradura de doble verificación

No solo puede resolver la seguridad de los hilos, sino también mejorar el rendimiento del programa, se propone un bloqueo de doble verificación

class SynchronizedSingleton{
    
    
    private static volatile SynchronizedSingleton instance =null;
    private SynchronizedSingleton(){
    
    }
    private static SynchronizedSingleton getInstance(){
    
    
        if (instance==null){
    
    
            synchronized (SynchronizedSingleton.class){
    
    
                if (instance==null){
    
    
                    return new SynchronizedSingleton();
                }
            }
        }
        return instance;
    }
}

Desventaja

  • Después de todo, la operación de bloqueo sincronizado todavía se usa, lo que afecta el rendimiento.

Implementación de clase interna

Utiliza el método de carga de la clase interna: la clase interna no se cargará cuando se inicialice la clase externa, y la clase interna se inicializará solo cuando se llame al método de la clase interna.
Es un singleton perezoso con el mejor rendimiento y seguridad de hilo.

class LazyInnerClassSingleton{
    
    
    private LazyInnerClassSingleton(){
    
    }

    public static final LazyInnerClassSingleton getInstance() {
    
    
        return LazyHoder.instance;
    }

    //延迟加载
    private static class LazyHoder{
    
    
        private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
    }
}

Reflejo destrucción singleton

Pero, ¿es perfecto el método anterior? Veamos primero un ejemplo de prueba

class LazyInnerClassSingletonTest{
    
    
    public static void main(String[] args) {
    
    

        try {
    
    
            Class<?>clazz=LazyInnerClassSingleton.class;
            Constructor c=clazz.getDeclaredConstructor(null);
             c.setAccessible(true);//强吻
             Object o1=c.newInstance();
             Object o2=LazyInnerClassSingleton.getInstance();
            System.out.println(o1==o2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Resultado de la operación: Se
Inserte la descripción de la imagen aquí
puede observar que aparecieron dos instancias diferentes cuando llamamos a su método de construcción por reflexión y llamamos al método getInstance ¿Cómo resolver esta situación?

class LazyInnerClassSingleton{
    
    
    private LazyInnerClassSingleton(){
    
    
        if(LazyHoder.instance!=null){
    
    
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    public static final LazyInnerClassSingleton getInstance() {
    
    
        return LazyHoder.instance;
    }

    //延迟加载
    private static class LazyHoder{
    
    
        private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
    }
}

A través del juicio y la excepción del método de construcción, el llamador solo puede obtener la instancia a través de getInstance

La serialización destruye singleton

Una vez que se crea un objeto, a veces es necesario serializar el objeto y escribirlo en el disco, y luego leer el objeto del disco la próxima vez que se use y deserializarlo para convertirlo en un objeto de memoria. El objeto después de la deserialización reasignará la memoria, es decir, la volverá a crear. Si el objeto serializado es un objeto singleton, destruirá el singleton

public class HungerSingleton implements Serializable{
    
    
    private static final HungerSingleton instance =new HungerSingleton();
    private HungerSingleton(){
    
    }
    public static HungerSingleton getInstance(){
    
    
        return instance;
    }
    public static HungerSingleton deepClone(HungerSingleton hs){
    
    
        HungerSingleton i =null;
        try {
    
    
            //序列化对象
            FileOutputStream fo=new FileOutputStream("HungerSingleton.obj");
            ObjectOutputStream os=new ObjectOutputStream(fo);
            os.writeObject(hs);

            os.flush();
            os.close();

            //反序列化对象
            FileInputStream fi=new FileInputStream("HungerSingleton.obj");
            ObjectInputStream ois=new ObjectInputStream(fi);
            i=(HungerSingleton)ois.readObject();
            ois.close();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return i;
    }

    public static void main(String[] args) {
    
    
        HungerSingleton i=HungerSingleton.getInstance();
        HungerSingleton ii=deepClone(i);
        System.out.println(i==ii);
    }
}

Resultado de la ejecución:
Inserte la descripción de la imagen aquí
obviamente, el objeto serializado no es coherente con el objeto creado manualmente y se instanciaron dos veces. Entonces, ¿cómo solucionar este problema? Es simple, solo agregareadResolveEl método está bien

public class HungerSingleton implements Serializable{
    
    
    private static final HungerSingleton instance =new HungerSingleton();
    private HungerSingleton(){
    
    }
    public static HungerSingleton getInstance(){
    
    
        return instance;
    }
     //序列化破坏单例的解决方案:
    //重写readResolve方法
    private Object readResolve(){
    
    
        return instance;
    }
}

Mire los resultados nuevamente:
Inserte la descripción de la imagen aquí

Singleton registrado

El singleton registrado también se denomina singleton registrado, lo que significa que cada instancia se registra en un lugar determinado y el singleton se obtiene con un identificador único.

Singleton enumerado

enum EnumSingleton{
    
    
    INSTANCE;
    private Object obj;

    private Object getObj(){
    
    
        return obj;
    }
    public static EnumSingleton getInstance(){
    
    
        return INSTANCE;
    }

    public static void main(String[] args) {
    
    
        EnumSingleton e1=EnumSingleton.getInstance();
        EnumSingleton e2=EnumSingleton.getInstance();
        System.out.println(e1==e2);

    }
}

El modo singleton de enumeración es un modo singleton recomendado en el libro "Effective Java"

Desventaja

  • Todos los objetos se inicializan en la memoria cuando se carga la clase. Como el estilo chino hambriento, no es adecuado para escenarios donde se crea una gran cantidad de objetos singleton.

ThreadLocal singleton

Anteriormente también hablamos sobre ThreadLocal, que es exclusivo de un solo hilo y es inherentemente seguro para subprocesos.

class ThreadLocalSingleton{
    
    
    private ThreadLocalSingleton(){
    
    }

    private static final ThreadLocal<ThreadLocalSingleton> instance=new ThreadLocal<ThreadLocalSingleton>(){
    
    
        @Override
        protected ThreadLocalSingleton initialValue() {
    
    
            return new ThreadLocalSingleton();
        }
    };

    private static ThreadLocalSingleton getInstance(){
    
    
        return instance.get();
    }
}

ThreadLocal singleton se puede considerar como un modo singleton especial. Si está interesado en el principio de ThreadLocal, puede leer el artículo anterior para comprender ThreadLocal en profundidad.

para resumir

Generalmente se recomienda usar el singleton chino hambriento. Solo cuando se logre claramente el efecto de carga diferida, se utilizará el patrón singleton implementado por la clase interna. Si se trata de deserialización para crear un objeto, puede intentar utilizar el primer método de enumeración. Si tiene otras necesidades especiales, puede considerar usar el método de bloqueo de doble verificación.

Supongo que te gusta

Origin blog.csdn.net/xzw12138/article/details/106620163
Recomendado
Clasificación