Está en la página 1de 7

Universidad Nacional Mayor de San Marcos

Facultad de Ingeniería de Sistemas e Informática


Escuela de Ingeniería de Sistemas

PRACTICA
CURSO : Diseño de Sistemas de Información.
No. : Práctica No.01.
TEMA : Práctica del Patrón de Diseño Singleton.
DURACIÓN ESTIMADA : 50 minutos aproximadamente.

I. OBJETIVOS

El objetivo de la guía es el de afianzar el conocimiento acerca del patrón Singleton para


resolver problemas reales con el uso del lenguaje de programación Java. Además, le
presentará las diferentes variantes de dicho patrón y los pros y contras de cada una.

II. IMPLEMENTACIÓN

Usos del Patrón


El patrón Singleton restringe la instanciación de una clase y asegura que solo una instancia
de la clase exista en la JVM. La clase Singleton debe proveer un mecanismo de acceso
global a dicha instancia. Este patrón es usado para el manejo de un Log, objetos de tipo
controlador, “cache” y pool de hilos.
También es utilizado junto con otros patrones de diseño como Abstract Factory, Builder,
Prototype, Facade, etc. Es utilizado dentro de algunas clases del núcleo de Java como son:
java.lang.Runtime y java.awt.Desktop.

Conceptos clave
Para implementar el patrón Singleton, tenemos diferentes enfoques, pero todos tienen en
común los siguientes conceptos:
● Un constructor privado para restringir la instanciación de la clase desde otras clases.
● Una variable estática privada de la misma clase la cual es la única instancia de la
clase.
● Un método estático público que retorna la instancia de la clase, este es el punto de
acceso global para que el resto del mundo pueda obtener la instancia de la clase
Singleton.

Inicialización anticipada
La instancia de la clase es creada cuando se carga la clase, es el método más fácil para
crear un Singleton, pero el inconveniente es que la instancia es creada aun cuando la
aplicación cliente no la utiliza.
Universidad Nacional Mayor de San Marcos
Facultad de Ingeniería de Sistemas e Informática
Escuela de Ingeniería de Sistemas

package com.journaldev.singleton;
public class EagerInitializedSingleton {

private static final EagerInitializedSingleton instance =


new EagerInitializedSingleton();

//private constructor to avoid client applications to use


constructor private EagerInitializedSingleton(){}

public static EagerInitializedSingleton getInstance(){


return instance;
}
}

Si la clase Singleton no utiliza muchos recursos, este es el enfoque para utilizar. Pero muchos
escenarios las clases Singleton son creadas para recursos tales como Sistema de Archivos,
conexiones a Bases de Datos, etc. y nosotros debemos evitar su instanciación hasta que por
lo menos el cliente llame al método getInstance. Además, este método no proporciona
manejo de excepciones.

Inicialización dentro de un bloque estático


Es similar al anterior excepto que la instancia de la clase es creada dentro de un bloque
estático que provee la opción de manejo de excepciones.

package com.journaldev.singleton;
public class StaticBlockSingleton {

private static StaticBlockSingleton instance;


private StaticBlockSingleton(){}

//static block initialization for exception


handling static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating "+
" singleton instance");
}
}

public static StaticBlockSingleton getInstance(){


return instance;
}
}

Tanto este enfoque como el anterior crean la instancia aún antes de ser utilizada y no es la
mejor práctica para utilizar.
Universidad Nacional Mayor de San Marcos
Facultad de Ingeniería de Sistemas e Informática
Escuela de Ingeniería de Sistemas

Inicialización relajada
Este método crea la instancia en el método de acceso global.

package com.journaldev.singleton;
public class LazyInitializedSingleton {

private static LazyInitializedSingleton instance;


private LazyInitializedSingleton(){}

public static LazyInitializedSingleton getInstance(){


if(instance == null){
instance = new LazyInitializedSingleton();
}
return instance;
}
}

Esta implementación trabaja bien en el caso de un entorno con un solo hilo de ejecución,
pero cuando se implementa dentro de sistema con múltiples hilos puede causar problemas
si varios hilos están dentro de if al mismo tiempo, ya que pueden destruir la instancia y ambos
hilos recibirán diferentes instancias de la clase.

Singleton seguro para los hilos


La manera más simple de crear un Singleton seguro para hilos es crear un método de acceso
global de tipo sinchronized tal que sólo un hilo pueda ejecutar este método a la vez.

package com.journaldev.singleton;
public class hreadSafeSingleton{

private static ThreadSafeSingleton instance;


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

Esto trabaja bien y provee seguridad en la ejecución con hilos, pero reduce la performance
debido al costo asociado con el método sinchronized, el cual sólo necesitamos para los pocos
primeros hilos que puedan crear instancias separadas. Para evitar esta sobrecarga extra
utilizamos el principio de cerradura de doble chequeo (doble checking locking). En este
enfoque el bloque sinchronized es usado dentro de la condición if con un chequeo adicional
para asegurar que sólo una instancia de la clase Singleton sea creada.

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){


if(instance == null){
synchronized (ThreadSafeSingleton.class)
Universidad Nacional Mayor de San Marcos
Facultad de Ingeniería de Sistemas e Informática
Escuela de Ingeniería de Sistemas

{
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}

La implementación de Bill Pugh


Antes de Java 5 el modelo de memoria de java tenía una cantidad de problemas y los
enfoques anteriores fallaban en ciertos escenarios donde muchos hilos intentaban obtener
la instancia del singleton de manera simultánea. Hasta Bill Pugh vino con un enfoque
diferente para crear el Singleton utilizando una clase estática de ayuda interna. La
implementación de Bill es la siguiente:

package com.journaldev.singleton;
public class BillPughSingleton {
private BillPughSingleton(){}

private static class SingletonHelper{


private static final BillPughSingleton INSTANCE =
new BillPughSingleton();
}

public static BillPughSingleton getInstance(){


return SingletonHelper.INSTANCE;
}
}

La clase interna contiene la instancia de la clase Singleton. Cuando la clase es cargada


SingletonHelper no será cargada en memoria y solo lo hará cuando se llame al método
getInstance, esta clase es cargada y crea la instancia.
Este es el enfoque más usado para clases Singleton que no requiere sincronización.

Usar Reflexión para destruir el patrón Singleton


La reflexión puede ser utilizada para destruir todos los enfoques anteriores de implementación
de un singleton.

package com.journaldev.singleton;
import java.lang.reflect.Constructor;
public class ReflectionSingletonTest {

public static void main(String[] args) {


EagerInitializedSingleton instanceOne =
EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
Universidad Nacional Mayor de San Marcos
Facultad de Ingeniería de Sistemas e Informática
Escuela de Ingeniería de Sistemas

try {
Constructor[] constructors =
EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
//Below code will destroy the singleton pattern
constructor.setAccessible(true);
instanceTwo =
(EagerInitializedSingleton) constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
}
}

Cuando se ejecuta este ejemplo Usted notará que el hashCode para ambas instancias no es
el mismo, lo que destruye el patrón Singleton. La Reflexión es muy potente y es utilizada en
gran cantidad de frameworks tales como Spring e Hibernate. Revise
https://www.journaldev.com/1789/java-reflection-example-tutorial

Singleton con Enumeración (Enum Singleton)


Para solucionar esta situación con la reflexión, Joshua Bloch sugiere el uso de Enum para
implementar el patrón de diseño Singleton porque Java asegura que cualquier valor de tipo
enum es instanciado solamente una vez en un programa Java. Como los valores enum de
Java son globalmente accesibles, vienen a ser un Singleton. La desventaja es que el tipo de
dato enum es algo inflexible: por ejemplo, no permite la inicialización relajada.

package com.journaldev.singleton;
public enum EnumSingleton {
INSTANCE;

public static void doSomething(){


//do something
}
}

La Serialización y el Singleton
A veces en sistemas distribuidos nosotros necesitamos implementar la interfaz Serializable
dentro de la clase Singleton de manera que podamos almacenar su estado en disco y
recuperarlo posteriormente en otro punto en el tiempo. Aquí hay un pequeño Singleton que
implementa la interfaz Serializable.

package com.journaldev.singleton;
Universidad Nacional Mayor de San Marcos
Facultad de Ingeniería de Sistemas e Informática
Escuela de Ingeniería de Sistemas

import java.io.Serializable;
public class SerializedSingleton implements Serializable{

private static final long serialVersionUID =


-7604766932017737115L;
private SerializedSingleton(){}
private static class SingletonHelper{
private static final SerializedSingleton instance =
new SerializedSingleton();
}

public static SerializedSingleton getInstance(){


return SingletonHelper.instance;
}
}

El problema con el Singleton serializado es que nosotros lo recuperemos, obtendremos una


nueva instancia de la clase. Veamos esto con un simple programa de prueba:

package com.journaldev.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class SingletonSerializedTest {

public static void main(String[] args)


throws FileNotFoundException, IOException, ClassNotFoundException {

SerializedSingleton instanceOne =
SerializedSingleton.getInstance();
ObjectOutput out =
new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instanceOne); out.close();

//deserailize from file to object


ObjectInput in =
new ObjectInputStream(new FileInputStream( "filename.ser"));
SerializedSingleton instanceTwo =
(SerializedSingleton) in.readObject();
in.close();

System.out.println("instanceOne hashCode="+instanceOne.hashCode());
Universidad Nacional Mayor de San Marcos
Facultad de Ingeniería de Sistemas e Informática
Escuela de Ingeniería de Sistemas

System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}
}

Si ejecuta este programa podrá ver que el valor de hashCode es distinto para ambas
instancias del Singleton, lo cual destruye el patrón, para solucionar esto necesitamos
proveer la implementación del método readResolve()

protected Object readResolve() {


return getInstance();
}

Después de esto notará que el hashCode de ambos será el mismo dentro del programa de
prueba.

Lecturas recomendadas
● Java Sincronización
https://www.journaldev.com/1061/thread-safety-in-java
● Java clases internas estáticas de ayuda
https://www.journaldev.com/996/java-inner-class
● Java clases anidadas
https://www.journaldev.com/996/java-inner-class
● Java Reflexión
https://www.journaldev.com/1789/java-reflection-example-tutorial
● Java enum
https://www.journaldev.com/716/java-enum
● Java Serialización
https://www.journaldev.com/927/objectoutputstream-java-write-object-file
● Java Deserialización
https://www.journaldev.com/933/objectinputstream-java-read-object-file

Bibliografía
1. Java Singleton Design Pattern Best Practices with Examples
https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-
examples
2. Simply Singleton
http://www.javaworld.com/article/2073352/core-java/simply-singleton.html

También podría gustarte