pattern - singleton java example
Singleton & Multithreading en Java (8)
¿Cuál es la forma preferida de trabajar con la clase Singleton en un entorno multiproceso?
Supongamos que si tengo 3 hilos y todos ellos intenten acceder al método getInstance()
de la clase singleton al mismo tiempo:
- ¿Qué pasaría si no se mantiene la sincronización?
- ¿Es una buena práctica usar el método
getInstance()
synchronized
o usarsynchronized
bloquesynchronized
dentro degetInstance()
?
Por favor avise si hay alguna otra salida.
¿Nadie usa Enums como se sugiere en Effective Java ?
Esta pregunta realmente depende de cómo y cuándo se crea su instancia. Si tu método getInstance
se getInstance
perezosamente:
if(instance == null){
instance = new Instance();
}
return instance
Luego debes sincronizar o podrías terminar con varias instancias. Este problema generalmente se trata en conversaciones sobre el bloqueo de doble comprobación .
De lo contrario, si creas una instancia estática por adelantado
private static Instance INSTANCE = new Instance();
entonces no es necesaria la sincronización del método getInstance()
.
La mejor manera como se describe en java eficaz es:
public class Singelton {
private static final Singelton singleObject = new Singelton();
public Singelton getInstance(){
return singleObject;
}
}
No hay necesidad de sincronización.
La tarea no es trivial en teoría, dado que quieres que sea realmente seguro para subprocesos.
Un buen artículo al respecto se encuentra en IBM.
Solo obtener el singleton no necesita ninguna sincronización, ya que es solo una lectura. Por lo tanto, simplemente sincronizar la configuración de la sincronización haría. A menos que dos pasos intenten crear el singleton al iniciarse al mismo tiempo, entonces debe asegurarse de verificar si la instancia está configurada dos veces (una fuera y otra dentro de la sincronización) para evitar restablecer la instancia en el peor de los casos.
Entonces es posible que deba tener en cuenta cómo los compiladores JIT manejan las escrituras fuera de orden. Este código estará de alguna manera cerca de la solución, aunque de todos modos no será 100% seguro para subprocesos:
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
Singleton inst = instance;
if (inst == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
}
}
return instance;
}
Entonces, tal vez deberías recurrir a algo menos perezoso:
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
O, un poco más hinchado, pero una forma más flexible es evitar el uso de singletons estáticos y el uso de un marco de inyección como Spring para administrar la creación de instancias de objetos "singleton-ish" (y puede configurar una inicialización perezosa).
Necesita la sincronización dentro de getInstance
solo si inicializa su singleton perezosamente. Si puede crear una instancia antes de que se inicien los subprocesos, puede soltar la sincronización en el getter, porque la referencia se vuelve inmutable. Por supuesto, si el objeto singleton en sí mismo es mutable, necesitaría sincronizar sus métodos para acceder a la información que se puede cambiar simultáneamente.
Para simplificar, creo que usar la clase enum es una mejor manera. No necesitamos hacer ninguna sincronización. Java por construcción, siempre asegúrese de que solo se haya creado una constante, sin importar cuántos subprocesos estén intentando acceder a ella.
Para su información, en algunos casos necesita intercambiar singleton con otra implementación. Luego necesitamos modificar la clase, que es una violación de Open Close principal. El problema con singleton es que no se puede extender la clase porque se tiene un constructor privado. Entonces, es una mejor práctica que el cliente esté hablando a través de la interfaz.
Implementación de Singleton con clase de enumeración e interfaz:
Client.java
public class Client{
public static void main(String args[]){
SingletonIface instance = EnumSingleton.INSTANCE;
instance.operationOnInstance("1");
}
}
SingletonIface.java
public interface SingletonIface {
public void operationOnInstance(String newState);
}
EnumSingleton.java
public enum EnumSingleton implements SingletonIface{
INSTANCE;
@Override
public void operationOnInstance(String newState) {
System.out.println("I am Enum based Singleton");
}
}
Si está hablando de la threadsafe perezosa de threadsafe , perezosa del singleton , aquí hay un patrón de código genial para usar que logra una inicialización perezosa 100% threadsafe sin ningún código de sincronización :
public class MySingleton {
private static class MyWrapper {
static MySingleton INSTANCE = new MySingleton();
}
private MySingleton () {}
public static MySingleton getInstance() {
return MyWrapper.INSTANCE;
}
}
Esto instanciará el singleton solo cuando se llame a getInstance()
, ¡y es 100% seguro para subprocesos! Es un clásico.
Funciona porque el cargador de clases tiene su propia sincronización para manejar la inicialización estática de clases: se le garantiza que toda la inicialización estática se completó antes de que se use la clase, y en este código la clase solo se usa dentro del método getInstance()
, por lo que es cuando la clase cargada carga la clase interna.
Aparte, espero con ansias el día en que @Singleton
una anotación @Singleton
que maneje dichos problemas.
Editado:
Un incrédulo en particular ha afirmado que la clase envoltura "no hace nada". Aquí está la prueba de que sí importa, aunque en circunstancias especiales.
La diferencia básica es que con la versión de la clase de envoltura, la instancia de singleton se crea cuando se carga la clase de envoltura, que cuando se realiza la primera llamada se hace getInstance()
, pero con la versión no ajustada, es decir, una simple inicialización estática: La instancia se crea cuando se carga la clase principal.
Si solo tiene una invocación simple del método getInstance()
, entonces casi no hay diferencia, la diferencia sería que todas las demás inicializaciones de sttic se hubieran completado antes de que se creara la instancia al usar la versión ajustada, pero esto se resuelve fácilmente. simplemente teniendo la variable de instancia estática listada en último lugar en la fuente.
Sin embargo, si está cargando la clase por nombre , la historia es bastante diferente. Invocar Class.forName(className)
en una clase para que ocurra la inicialización estática, de modo que si la clase singleton que se usará es una propiedad de su servidor, con la versión simple se creará la instancia estática cuando se llame a Class.forName()
no cuando se llama a getInstance()
. Admito que esto es un poco artificial, ya que necesita usar la reflexión para obtener la instancia, pero no obstante, aquí hay un código de trabajo completo que demuestra mi opinión (cada una de las siguientes clases es una clase de nivel superior):
public abstract class BaseSingleton {
private long createdAt = System.currentTimeMillis();
public String toString() {
return getClass().getSimpleName() + " was created " + (System.currentTimeMillis() - createdAt) + " ms ago";
}
}
public class EagerSingleton extends BaseSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
public class LazySingleton extends BaseSingleton {
private static class Loader {
static final LazySingleton INSTANCE = new LazySingleton();
}
public static LazySingleton getInstance() {
return Loader.INSTANCE;
}
}
Y el principal:
public static void main(String[] args) throws Exception {
// Load the class - assume the name comes from a system property etc
Class<? extends BaseSingleton> lazyClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.LazySingleton");
Class<? extends BaseSingleton> eagerClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.EagerSingleton");
Thread.sleep(1000); // Introduce some delay between loading class and calling getInstance()
// Invoke the getInstace method on the class
BaseSingleton lazySingleton = (BaseSingleton) lazyClazz.getMethod("getInstance").invoke(lazyClazz);
BaseSingleton eagerSingleton = (BaseSingleton) eagerClazz.getMethod("getInstance").invoke(eagerClazz);
System.out.println(lazySingleton);
System.out.println(eagerSingleton);
}
Salida:
LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago
Como puede ver, la implementación simple no ajustada se crea cuando se llama a Class.forName()
, lo que puede ocurrir antes de que la inicialización estática esté lista para ejecutarse.
Si está seguro de que su tiempo de ejecución de Java está utilizando el nuevo JMM (modelo de memoria Java, probablemente más nuevo que 5.0) , el bloqueo de doble verificación está bien, pero agregue un elemento volátil delante de la instancia. De lo contrario, será mejor que uses una clase interna estática como dijo Bohemian, o Enum en ''Java efectiva'' como dijo Florian Salihovic.