CodeGym/Blog Java/Random-ES/Clase única de Java
Autor
Artem Divertitto
Senior Android Developer at United Tech

Clase única de Java

Publicado en el grupo Random-ES
¡Hola! Hoy nos sumergiremos en los detalles de varios patrones de diseño, comenzando con el patrón Java Singleton. Repasemos: ¿qué sabemos sobre los patrones de diseño en general? Los patrones de diseño son las mejores prácticas que podemos aplicar para resolver una serie de problemas conocidos. Los patrones de diseño generalmente no están vinculados a ningún lenguaje de programación. Piense en ellos como un conjunto de recomendaciones para ayudarlo a evitar errores y evitar reinventar la rueda.Patrones de diseño: Singleton - 1

¿Qué es un singleton en Java?

Singleton es uno de los patrones de diseño de nivel de clase más simples. A veces, la gente dice "esta clase es singleton", lo que significa que la clase implementa el patrón de diseño singleton. A veces, es necesario escribir una clase en la que restringimos la creación de instancias a un solo objeto. Por ejemplo, una clase responsable de iniciar sesión o conectarse a un base de datos. El patrón de diseño singleton describe cómo podemos lograr esto. Un singleton es un patrón de diseño que hace dos cosas:
  1. Garantiza que solo habrá una instancia de la clase.

  2. Proporciona un único punto de acceso global a esa instancia.

Por lo tanto, hay dos características que son características de casi todas las implementaciones del patrón singleton:
  1. Un constructor privado. Esto limita la capacidad de crear objetos de la clase fuera de la clase misma.

  2. Un método estático público que devuelve la instancia de la clase. Este método se llama getInstance . Este es el punto de acceso global a la instancia de la clase.

Opciones de implementación

El patrón de diseño singleton se aplica de varias formas. Cada opción es buena y mala a su manera. Como siempre, no hay una opción perfecta aquí, pero debemos esforzarnos por encontrar una. En primer lugar, decidamos qué constituye bueno y malo, y qué métricas afectan la forma en que evaluamos las diversas implementaciones del patrón de diseño. Empecemos por lo bueno. Estos son los factores que hacen que una implementación sea más jugosa y atractiva:
  • Inicialización diferida: la instancia no se crea hasta que se necesita.

  • Código simple y transparente: esta métrica, por supuesto, es subjetiva, pero es importante.

  • Seguridad de subprocesos: correcto funcionamiento en un entorno multiproceso.

  • Alto rendimiento en un entorno de subprocesos múltiples: poco o ningún bloqueo de subprocesos al compartir un recurso.

Ahora los contras. Enumeraremos los factores que ponen una implementación en una mala posición:
  • Sin inicialización perezosa: cuando la clase se carga cuando se inicia la aplicación, independientemente de si se necesita o no (paradójicamente, en el mundo de TI es mejor ser perezoso)

  • Código complejo y difícil de leer. Esta métrica también es subjetiva. Si sus ojos comienzan a sangrar, asumiremos que la implementación no es la mejor.

  • Falta de seguridad del hilo. En otras palabras, "peligro de hilo". Funcionamiento incorrecto en un entorno de subprocesos múltiples.

  • Rendimiento deficiente en un entorno de subprocesos múltiples: los subprocesos se bloquean entre sí todo el tiempo o con frecuencia al compartir un recurso.

Código

Ahora estamos listos para considerar varias opciones de implementación e indicar los pros y los contras:

Simple

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
La implementación más simple. Ventajas:
  • Código simple y transparente

  • seguridad del hilo

  • Alto rendimiento en un entorno de subprocesos múltiples

Contras:
  • Sin inicialización perezosa.
En un intento de corregir la deficiencia anterior, obtenemos la implementación número dos:

Inicialización perezosa

public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Ventajas:
  • Inicialización perezosa.

Contras:
  • No es seguro para subprocesos

Esta implementación es interesante. Podemos inicializar perezosamente, pero hemos perdido la seguridad de subprocesos. No se preocupe, sincronizamos todo en la implementación número tres.

Acceso sincronizado

public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Ventajas:
  • Inicialización perezosa.

  • seguridad del hilo

Contras:
  • Pobre rendimiento multiproceso

¡Excelente! En la implementación número tres, restauramos la seguridad de subprocesos. Por supuesto, es lento... Ahora el método getInstance está sincronizado, por lo que solo puede ser ejecutado por un subproceso a la vez. En lugar de sincronizar todo el método, en realidad solo necesitamos sincronizar la parte que inicializa la nueva instancia. Pero no podemos simplemente usar un bloque sincronizado para envolver la parte responsable de crear la nueva instancia. Hacer eso no garantizaría la seguridad del subproceso. Es todo un poco más complicado. La sincronización adecuada se puede ver a continuación:

Bloqueo de doble control

public class Singleton {
    private static final Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Ventajas:
  • Inicialización perezosa.

  • seguridad del hilo

  • Alto rendimiento en un entorno de subprocesos múltiples

Contras:
  • No se admite en versiones anteriores de Java inferiores a 1.5 (el uso de la palabra clave volatile es fijo desde la versión 1.5)

Tenga en cuenta que para que esta opción de implementación funcione correctamente, se debe cumplir una de dos condiciones. La variable INSTANCE debe ser final o volátil . La última implementación de la que hablaremos hoy es el class holder singleton .

Titular de la clase

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Ventajas:
  • Inicialización perezosa.

  • Seguridad del hilo.

  • Alto rendimiento en un entorno de subprocesos múltiples.

Contras:
  • El funcionamiento correcto requiere una garantía de que el objeto singleton se inicializa sin errores. De lo contrario, la primera llamada al método getInstance generará un ExceptionInInitializerError y todas las llamadas posteriores generarán un NoClassDefFoundError .

Esta implementación es casi perfecta. Es perezoso, seguro para subprocesos y rápido. Pero tiene un matiz, como se explica en la lista de contras. Comparación de varias implementaciones del patrón singleton:
Implementación Inicialización perezosa seguridad del hilo Rendimiento multiproceso ¿Cuándo usar?
Simple - + Rápido Nunca. O posiblemente cuando la inicialización diferida no es importante. Pero nunca sería mejor.
Inicialización perezosa + - No aplica Siempre que no se necesite subprocesamiento múltiple
Acceso sincronizado + + Lento Nunca. O posiblemente cuando el rendimiento multiproceso no importa. Pero nunca sería mejor.
Bloqueo de doble control + + Rápido En casos excepcionales, cuando necesita manejar excepciones al crear el singleton (cuando el singleton del titular de la clase no es aplicable)
Titular de la clase + + Rápido Siempre que se necesiten subprocesos múltiples y haya garantía de que el objeto singleton se creará sin problemas.

Pros y contras del patrón singleton

En general, un singleton hace exactamente lo que se espera de él:
  1. Garantiza que solo habrá una instancia de la clase.

  2. Proporciona un único punto de acceso global a esa instancia.

Sin embargo, este patrón tiene deficiencias:
  1. Un singleton viola el principio de responsabilidad única: además de sus funciones directas, la clase singleton también controla el número de instancias.

  2. La dependencia de una clase ordinaria de un singleton no es visible en el contrato público de la clase.

  3. Las variables globales son malas. En última instancia, un singleton se convierte en una variable global considerable.

  4. La presencia de un singleton reduce la capacidad de prueba de la aplicación en su conjunto y de las clases que utilizan el singleton en particular.

¡Y eso es! :) Hemos explorado la clase Java Singleton contigo. Ahora, por el resto de tu vida, cuando converses con tus amigos programadores, puedes mencionar no solo lo bueno que es el patrón, sino también algunas palabras sobre lo que lo hace malo. Buena suerte dominando este nuevo conocimiento.

Lectura adicional:

Comentarios
  • Populares
  • Nuevas
  • Antiguas
Debes iniciar sesión para dejar un comentario
Esta página aún no tiene comentarios