¿Cuál es una forma eficiente de implementar un patrón singleton en Java?
¿Cuál es una forma eficiente de implementar un patrón singleton en Java?
Respuestas:
Use una enumeración:
public enum Foo {
INSTANCE;
}
Joshua Bloch explicó este enfoque en su charla Efectiva Java Reloaded en Google I / O 2008: enlace al video . También vea las diapositivas 30-32 de su presentación ( efectiva_java_reloaded.pdf ):
La forma correcta de implementar un Singleton serializable
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Editar: Una parte en línea de "Java efectivo" dice:
"Este enfoque es funcionalmente equivalente al enfoque de campo público, excepto que es más conciso, proporciona la maquinaria de serialización de forma gratuita y proporciona una garantía irrefrenable contra la creación de instancias múltiples, incluso frente a ataques de reflexión o serialización sofisticados. Si bien este enfoque tiene aún por ser ampliamente adoptado, un tipo de enumeración de elemento único es la mejor manera de implementar un singleton ".
Dependiendo del uso, hay varias respuestas "correctas".
Desde java5, la mejor manera de hacerlo es usar una enumeración:
public enum Foo {
INSTANCE;
}
Pre java5, el caso más simple es:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
Repasemos el código. Primero, quieres que la clase sea final. En este caso, he usado la final
palabra clave para que los usuarios sepan que es definitiva. Luego debe hacer que el constructor sea privado para evitar que los usuarios creen su propio Foo. Lanzar una excepción del constructor evita que los usuarios usen la reflexión para crear un segundo Foo. Luego, crea un private static final Foo
campo para contener la única instancia y un public static Foo getInstance()
método para devolverlo. La especificación Java asegura que solo se llama al constructor cuando se usa la clase por primera vez.
Cuando tiene un objeto muy grande o un código de construcción pesado Y también tiene otros métodos o campos estáticos accesibles que pueden usarse antes de que se necesite una instancia, entonces y solo entonces debe usar una inicialización diferida.
Puede usar a private static class
para cargar la instancia. El código entonces se vería así:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
Dado que la línea private static final Foo INSTANCE = new Foo();
solo se ejecuta cuando se usa realmente la clase FooLoader, esto se ocupa de la instanciación diferida y se garantiza que sea segura para subprocesos.
Cuando también desea poder serializar su objeto, debe asegurarse de que la deserialización no cree una copia.
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
El método readResolve()
se asegurará de que se devuelva la única instancia, incluso cuando el objeto se serializó en una ejecución anterior de su programa.
Descargo de responsabilidad: acabo de resumir todas las respuestas increíbles y lo escribí en mis palabras.
Mientras implementamos Singleton, tenemos 2 opciones
1. Carga diferida
2. Carga temprana
La carga diferida agrega un poco de sobrecarga (para ser sincero), así que úselo solo cuando tenga un objeto muy grande o un código de construcción pesado Y también tenga otros métodos o campos estáticos accesibles que puedan usarse antes de que se necesite una instancia, entonces y solo entonces necesita usar una inicialización diferida. De lo contrario, elegir la carga temprana es una buena opción.
La forma más sencilla de implementar Singleton es
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
Todo está bien, excepto su singleton cargado temprano. Probemos singleton cargado perezoso
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
Hasta ahora todo bien, pero nuestro héroe no sobrevivirá mientras lucha solo con múltiples hilos malvados que quieren muchas instancias de nuestro héroe. Así que vamos a protegerlo del mal multihilo
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
pero no es suficiente para proteger al héroe, ¡¡¡En serio !!! Esto es lo mejor que podemos / debemos hacer para ayudar a nuestro héroe
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
Esto se llama "modismo de bloqueo de doble verificación". Es fácil olvidar la declaración volátil y difícil de entender por qué es necesario.
Para más detalles: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Ahora estamos seguros sobre el hilo del mal, pero ¿qué pasa con la cruel serialización? Tenemos que asegurarnos de que incluso durante la deserialización no se cree ningún objeto nuevo
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// Rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
El método readResolve()
se asegurará de que se devuelva la única instancia, incluso cuando el objeto se serializó en una ejecución anterior de nuestro programa.
Finalmente, hemos agregado suficiente protección contra hilos y serialización, pero nuestro código se ve voluminoso y feo. Vamos a darle un cambio a nuestro héroe
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Sí, este es nuestro mismo héroe :)
Desde la líneaprivate static final Foo INSTANCE = new Foo();
solo se ejecuta cuando la clase FooLoader
se usa realmente, esto se encarga de la instanciación perezosa,
y se garantiza que sea seguro para subprocesos.
Y hemos llegado tan lejos, aquí está la mejor manera de lograr todo lo que hicimos, es la mejor manera posible
public enum Foo {
INSTANCE;
}
Que internamente será tratado como
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
¡Eso es! No más miedo a la serialización, hilos y código feo. También ENUMS singleton están perezosamente inicializados .
Este enfoque es funcionalmente equivalente al enfoque de campo público, excepto que es más conciso, proporciona la maquinaria de serialización de forma gratuita y ofrece una garantía irrefrenable contra la creación de instancias múltiples, incluso ante ataques de reflexión o serialización sofisticados. Si bien este enfoque aún no se ha adoptado ampliamente, un tipo de enumeración de un solo elemento es la mejor manera de implementar un singleton.
-Joshua Bloch en "Java efectivo"
Ahora te habrás dado cuenta de por qué las ENUMS se consideran la mejor manera de implementar Singleton y gracias por tu paciencia :) Lo
actualicé en mi blog .
serialVersionUID
de 0L
. Tercer problema: Sin personalización: Cualquier método writeObject, readObject, readObjectNoData, writeReplace y readResolve específico de la clase definido por los tipos de enumeración se ignora durante la serialización y la deserialización.
La solución publicada por Stu Thompson es válida en Java5.0 y posterior. Pero preferiría no usarlo porque creo que es propenso a errores.
Es fácil olvidar la declaración volátil y difícil de entender por qué es necesario. Sin el volátil, este código ya no sería seguro para subprocesos debido al antipatrón de bloqueo doblemente verificado. Vea más sobre esto en el párrafo 16.2.4 de la concurrencia de Java en la práctica . En resumen: este patrón (anterior a Java5.0 o sin la declaración volátil) podría devolver una referencia al objeto Bar que (todavía) está en un estado incorrecto.
Este patrón fue inventado para la optimización del rendimiento. Pero esto ya no es una preocupación real. El siguiente código de inicialización diferida es rápido y, lo que es más importante, más fácil de leer.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (Y si getBar
se le llama "demasiado pronto", entonces enfrentará el mismo problema sin importar cómo se implementen los solteros.) Puede ver la carga de clase diferida del código anterior aquí: pastebin.com/iq2eayiR
Hilo seguro en Java 5+:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
EDITAR : Presta atención al volatile
modificador aquí. :) Es importante porque sin él, el JMM (Java Memory Model) no garantiza otros hilos para ver cambios en su valor. La sincronización no se ocupa de eso, solo serializa el acceso a ese bloque de código.
EDIT 2 : la respuesta de @Bno detalla el enfoque recomendado por Bill Pugh (FindBugs) y es discutible mejor. Ve a leer y vota su respuesta también.
Olvídate de la inicialización perezosa , es demasiado problemático. Esta es la solución más simple:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Asegúrate de que realmente lo necesitas. Haga un google para "singleton anti-pattern" para ver algunos argumentos en su contra. Supongo que no tiene nada de intrínseco, pero es solo un mecanismo para exponer algunos recursos / datos globales, así que asegúrese de que esta sea la mejor manera. En particular, he encontrado que la inyección de dependencia es más útil, especialmente si también está utilizando pruebas unitarias porque DI le permite usar recursos simulados para fines de prueba.
Estoy desconcertado por algunas de las respuestas que sugieren DI como una alternativa al uso de singletons; Estos son conceptos no relacionados. Puede usar DI para inyectar instancias singleton o no singleton (por ejemplo, por subproceso). Al menos esto es cierto si usa Spring 2.x, no puedo hablar por otros marcos DI.
Por lo tanto, mi respuesta al OP sería (en todos, excepto el código de muestra más trivial) a:
Este enfoque le brinda una buena arquitectura desacoplada (y, por lo tanto, flexible y comprobable) donde usar un singleton es un detalle de implementación fácilmente reversible (siempre que cualquier singleton que use sea seguro para subprocesos).
TicketNumberer
qué debe tener una única instancia global y dónde desea escribir una clase TicketIssuer
que contenga una línea de código int ticketNumber = ticketNumberer.nextTicketNumber();
. En el pensamiento tradicional de singleton, la línea de código anterior tendría que ser algo así TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. En el pensamiento DI, la clase tendría un constructor como public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.
main
método de la aplicación (o uno de sus secuaces) crearía la dependencia y luego llamaría al constructor. Esencialmente, el uso de una variable global (o un método global) es solo una forma simple del temido patrón localizador de servicios , y puede reemplazarse por inyección de dependencia, al igual que cualquier otro uso de ese patrón.
Considere realmente por qué necesita un singleton antes de escribirlo. Existe un debate cuasirreligioso sobre su uso que puede tropezar fácilmente si busca en Google singletons en Java.
Personalmente, trato de evitar los singletons con la mayor frecuencia posible por muchas razones, una vez más, la mayoría de las cuales se pueden encontrar buscando en google los singletons. Siento que a menudo se abusa de los singletons porque son fáciles de entender por todos, se usan como un mecanismo para obtener datos "globales" en un diseño OO y se usan porque es fácil eludir la gestión del ciclo de vida de los objetos (o realmente pensando en cómo puedes hacer A desde adentro B). Mire cosas como Inversion of Control (IoC) o Dependency Injection (DI) para un buen término medio.
Si realmente necesita uno, wikipedia tiene un buen ejemplo de una implementación adecuada de un singleton.
Los siguientes son 3 enfoques diferentes
1) Enum
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Doble comprobación de bloqueo / carga diferida
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) método de fábrica estático
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
Utilizo Spring Framework para administrar mis singletons. No impone el "singleton-ness" de la clase (lo cual no se puede hacer de todos modos si hay varios cargadores de clases involucrados), pero proporciona una forma realmente fácil de construir y configurar diferentes fábricas para crear diferentes tipos de objetos.
Versión 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Carga perezosa, hilo seguro con bloqueo, bajo rendimiento debido a synchronized
.
Versión 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
Carga perezosa, hilo seguro con sin bloqueo, alto rendimiento.
Si no necesita carga lenta, simplemente intente
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Si desea una carga lenta y desea que su Singleton sea seguro para subprocesos, pruebe el patrón de doble verificación
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Como no se garantiza que el patrón de doble verificación funcione (debido a algún problema con los compiladores, no sé nada más sobre eso), también podría intentar sincronizar todo el método getInstance o crear un registro para todos sus Singletons.
volatile
Yo diría Enum Singleton
Singleton usando enum en Java es generalmente una forma de declarar enum singleton. Enum singleton puede contener variable de instancia y método de instancia. En aras de la simplicidad, también tenga en cuenta que si está utilizando cualquier método de instancia de lo que necesita para garantizar la seguridad del hilo de ese método si afecta el estado del objeto.
El uso de una enumeración es muy fácil de implementar y no tiene inconvenientes con respecto a los objetos serializables, que deben ser evitados de otras maneras.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Puede acceder a él de manera Singleton.INSTANCE
mucho más fácil que llamar al getInstance()
método en Singleton.
1.12 Serialización de constantes de enumeración
Las constantes de enumeración se serializan de manera diferente a los objetos serializables o externalizables ordinarios. La forma serializada de una constante enum consiste únicamente en su nombre; Los valores de campo de la constante no están presentes en el formulario. Para serializar una constante enum,
ObjectOutputStream
escribe el valor devuelto por el método de nombre de la constante enum. Para deserializar una constante enum,ObjectInputStream
lee el nombre constante de la secuencia; la constante deserializada se obtiene llamando aljava.lang.Enum.valueOf
método, pasando el tipo de enumeración de la constante junto con el nombre de la constante recibida como argumentos. Al igual que otros objetos serializables o externalizables, las constantes de enumeración pueden funcionar como objetivos de referencias posteriores que aparecen posteriormente en la secuencia de serialización.El proceso por el cual se serializan constantes de enumeración no se pueden personalizar: cualquier clase específica
writeObject
,readObject
,readObjectNoData
,writeReplace
, yreadResolve
métodos definidos por los tipos enum son ignorados durante la serialización y deserialización. Del mismo modo, cualquierserialPersistentFields
oserialVersionUID
de campo declaraciones también se ignoran - todos los tipos de enumeración se fijaserialVersionUID
de0L
. Documentar campos y datos serializables para tipos de enumeración es innecesario, ya que no hay variación en el tipo de datos enviados.
Otro problema con Singletons convencionales es que una vez que implementa la Serializable
interfaz, ya no siguen siendo Singleton porque el readObject()
método siempre devuelve una nueva instancia como constructor en Java. Esto puede evitarse usando readResolve()
y descartando una instancia recién creada reemplazando con singleton como se muestra a continuación
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Esto puede volverse aún más complejo si su Clase Singleton mantiene el estado, ya que necesita hacerlos transitorios, pero con Enum Singleton, JVM garantiza la serialización.
Buena lectura
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
Puede que sea un poco tarde para el juego en esto, pero hay muchos matices en torno a la implementación de un singleton. El patrón de soporte no se puede usar en muchas situaciones. E IMO cuando se usa un volátil, también debe usar una variable local. Comencemos por el principio e iteremos sobre el problema. Verás a qué me refiero.
El primer intento podría verse más o menos así:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Aquí tenemos la clase MySingleton que tiene un miembro estático privado llamado INSTANCE, y un método estático público llamado getInstance (). La primera vez que se llama a getInstance (), el miembro INSTANCE es nulo. El flujo caerá en la condición de creación y creará una nueva instancia de la clase MySingleton. Las llamadas posteriores a getInstance () encontrarán que la variable INSTANCE ya está establecida y, por lo tanto, no crearán otra instancia de MySingleton. Esto garantiza que solo haya una instancia de MySingleton que se comparta entre todos los que llaman de getInstance ().
Pero esta implementación tiene un problema. Las aplicaciones multiproceso tendrán una condición de carrera en la creación de la instancia única. Si varios subprocesos de ejecución golpean el método getInstance () al mismo tiempo (o alrededor de ellos), cada uno verá el miembro INSTANCE como nulo. Esto dará como resultado que cada hilo cree una nueva instancia de MySingleton y, posteriormente, configure el miembro INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Aquí hemos usado la palabra clave sincronizada en la firma del método para sincronizar el método getInstance (). Esto ciertamente arreglará nuestra condición de carrera. Los hilos ahora se bloquearán e ingresarán al método de uno en uno. Pero también crea un problema de rendimiento. Esta implementación no solo sincroniza la creación de la instancia única, sino que sincroniza todas las llamadas a getInstance (), incluidas las lecturas. Las lecturas no necesitan ser sincronizadas ya que simplemente devuelven el valor de INSTANCE. Dado que las lecturas constituirán la mayor parte de nuestras llamadas (recuerde, la creación de instancias solo ocurre en la primera llamada), incurriremos en un rendimiento innecesario al sincronizar todo el método.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Aquí hemos movido la sincronización de la firma del método a un bloque sincronizado que envuelve la creación de la instancia de MySingleton. ¿Pero esto resuelve nuestro problema? Bueno, ya no estamos bloqueando las lecturas, pero también hemos dado un paso atrás. Varios subprocesos alcanzarán el método getInstance () aproximadamente al mismo tiempo y todos verán al miembro INSTANCE como nulo. Luego golpearán el bloque sincronizado donde se obtendrá el bloqueo y creará la instancia. Cuando ese hilo sale del bloque, los otros hilos competirán por el bloqueo, y uno por uno cada hilo caerá a través del bloque y creará una nueva instancia de nuestra clase. Así que estamos de vuelta donde comenzamos.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Aquí emitimos otro cheque DENTRO del bloque. Si el miembro de INSTANCE ya se ha configurado, omitiremos la inicialización. Esto se llama bloqueo de doble verificación.
Esto resuelve nuestro problema de instanciación múltiple. Pero una vez más, nuestra solución ha presentado otro desafío. Es posible que otros hilos no "vean" que el miembro de INSTANCE ha sido actualizado. Esto se debe a cómo Java optimiza las operaciones de memoria. Los subprocesos copian los valores originales de las variables de la memoria principal en la memoria caché de la CPU. Los cambios en los valores se escriben y leen desde ese caché. Esta es una característica de Java diseñada para optimizar el rendimiento. Pero esto crea un problema para nuestra implementación de singleton. Un segundo subproceso, que está siendo procesado por una CPU o núcleo diferente, usando un caché diferente, no verá los cambios realizados por el primero. Esto hará que el segundo subproceso vea al miembro de INSTANCE como nulo, lo que obligará a crear una nueva instancia de nuestro singleton.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Resolvemos esto usando la palabra clave volátil en la declaración del miembro de INSTANCE. Esto le indicará al compilador que siempre lea y escriba en la memoria principal y no en la memoria caché de la CPU.
Pero este simple cambio tiene un costo. Debido a que estamos evitando el caché de la CPU, tomaremos un impacto en el rendimiento cada vez que operemos en el miembro volátil INSTANCE, lo que hacemos 4 veces. Verificamos dos veces la existencia (1 y 2), establecemos el valor (3) y luego devolvemos el valor (4). Se podría argumentar que este camino es el caso marginal, ya que solo creamos la instancia durante la primera llamada del método. Quizás un golpe de rendimiento en la creación es tolerable. Pero incluso nuestro caso de uso principal, lee, operará en el miembro volátil dos veces. Una vez para verificar la existencia, y otra vez para devolver su valor.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
Dado que el impacto en el rendimiento se debe a que opera directamente en el miembro volátil, establezcamos una variable local en el valor del volátil y, en su lugar, operemos en la variable local. Esto disminuirá la cantidad de veces que operamos en el volátil, recuperando así parte de nuestro rendimiento perdido. Tenga en cuenta que tenemos que establecer nuestra variable local nuevamente cuando ingresamos al bloque sincronizado. Esto garantiza que esté actualizado con los cambios que ocurrieron mientras esperábamos el bloqueo.
Escribí un artículo sobre esto recientemente. Deconstruyendo The Singleton . Puede encontrar más información sobre estos ejemplos y un ejemplo del patrón "titular" allí. También hay un ejemplo del mundo real que muestra el enfoque volátil doblemente verificado. Espero que esto ayude.
class MySingleton
tal, tal vez debería ser final
?
BearerToken
instancia no es estática porque es parte de la BearerTokenFactory
configuración de un servidor de autorización específico. Podría haber muchos BearerTokenFactory
objetos, cada uno con su propio "caché" BearerToken
que entrega hasta que haya expirado. El hasExpired()
método en el BeraerToken
se llama en el get()
método de fábrica para garantizar que no entregue un token caducado. Si caducó, se solicitará un nuevo token para el servidor de autorización. El párrafo que sigue al bloque de código explica esto con mayor detalle.
Así es como implementar un simple singleton
:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
Así es como crear la pereza correctamente singleton
:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()
. Pero, de hecho, si no tiene ningún otro método estático en su clase Singleton
y solo llama, getInstance()
no hay una diferencia real.
Necesita un lenguaje de verificación doble si necesita cargar la variable de instancia de una clase perezosamente. Si necesita cargar una variable estática o un singleton de manera perezosa, necesita un idioma de titularización de inicialización bajo demanda .
Además, si el singleton necesita ser serilizable, todos los demás campos deben ser transitorios y el método readResolve () debe implementarse para mantener el objeto singleton invariable. De lo contrario, cada vez que se deserialice el objeto, se creará una nueva instancia del objeto. Lo que readResolve () hace es reemplazar el nuevo objeto leído por readObject (), lo que obligó a ese nuevo objeto a ser recolectado como basura ya que no hay ninguna variable que se refiera a él.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
Varias formas de hacer un objeto singleton:
Según Joshua Bloch, Enum sería el mejor.
También puede utilizar el bloqueo de doble verificación.
Incluso se puede usar la clase estática interna.
Enum singleton
La forma más sencilla de implementar un Singleton que sea seguro para subprocesos es usar un Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
Este código funciona desde la introducción de Enum en Java 1.5
Doble control de bloqueo
Si desea codificar un singleton "clásico" que funciona en un entorno multiproceso (a partir de Java 1.5), debe utilizar este.
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
Esto no es seguro para subprocesos antes de 1.5 porque la implementación de la palabra clave volátil fue diferente.
Singleton de carga temprana (funciona incluso antes de Java 1.5)
Esta implementación instancia el singleton cuando se carga la clase y proporciona seguridad de subprocesos.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
Para JSE 5.0 y superior, adopte el enfoque Enum; de lo contrario, utilice el enfoque de soporte singleton estático ((un enfoque de carga diferida descrito por Bill Pugh). La última solución también es segura para subprocesos sin requerir construcciones de lenguaje especiales (es decir, volátil o sincronizado).
Otro argumento a menudo utilizado contra Singletons son sus problemas de comprobabilidad. Los singletons no se pueden burlar fácilmente con fines de prueba. Si esto resulta ser un problema, me gustaría hacer la siguiente pequeña modificación:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
El setInstance
método agregado permite configurar una implementación de maqueta de la clase singleton durante las pruebas:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Esto también funciona con enfoques de inicialización temprana:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Esto tiene el inconveniente de exponer esta funcionalidad a la aplicación normal también. Otros desarrolladores que trabajan en ese código podrían verse tentados a usar el método 'setInstance' para alterar, alterar una función específica y, por lo tanto, cambiar el comportamiento completo de la aplicación, por lo tanto, este método debe contener al menos una buena advertencia en su javadoc.
Aún así, para la posibilidad de realizar pruebas de prueba (cuando sea necesario), esta exposición del código puede ser un precio aceptable a pagar.
clase singleton más simple
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Todavía creo que después de Java 1.5, enum es la mejor implementación de singleton disponible, ya que también garantiza que incluso en entornos de subprocesos múltiples, solo se crea una instancia.
public enum Singleton{
INSTANCE;
}
y listo!
Echa un vistazo a esta publicación.
Ejemplos de patrones de diseño de GoF en las bibliotecas principales de Java
De la sección "Singleton" de la mejor respuesta,
Singleton (reconocible por métodos de creación que devuelven la misma instancia (generalmente de sí mismo) cada vez)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
También puede aprender el ejemplo de Singleton de las clases nativas de Java.
El mejor patrón singleton que he visto utiliza la interfaz del proveedor.
Vea abajo:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
A veces un simple " static Foo foo = new Foo();
" no es suficiente. Solo piense en la inserción de datos básicos que desea hacer.
Por otro lado, tendría que sincronizar cualquier método que instanciara la variable singleton como tal. La sincronización no es mala como tal, pero puede conducir a problemas de rendimiento o bloqueo (en situaciones muy raras con este ejemplo. La solución es
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
Ahora que pasa? La clase se carga a través del cargador de clases. Directamente después de que la clase fue interpretada desde una matriz de bytes, la VM ejecuta el bloque estático {} . ese es todo el secreto: el bloque estático solo se llama una vez, el tiempo en que la clase (nombre) del paquete dado es cargada por este cargador de clase.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
if (INSTANCE != null)
throw new IllegalStateException (“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
Como hemos agregado la palabra clave Sincronizada antes de getInstance, hemos evitado la condición de carrera en el caso en que dos hilos llaman a getInstance al mismo tiempo.