1. ¿Qué es el modo singleton?
El patrón Singleton se refiere a la existencia de una sola instancia durante toda la vida de la aplicación. El patrón Singleton es un patrón de diseño ampliamente utilizado. Tiene muchos beneficios, que pueden evitar la creación duplicada de objetos de instancia, reducir la sobrecarga del sistema de crear instancias y guardar memoria.
Hay tres requisitos para el modo Singleton:
2. La diferencia entre el patrón singleton y la clase estática
Primero, entendamos qué es una clase estática. Una clase estática significa que una clase tiene métodos estáticos y campos estáticos. El constructor es modificado por privado, por lo que no se puede instanciar. La clase de matemáticas es una clase estática.
Después de saber qué es una clase estática, hablemos de la diferencia entre ellos:
1) En primer lugar, el patrón Singleton le proporcionará un objeto único a nivel mundial. La clase estática solo le proporciona muchos métodos estáticos. Estos métodos no necesitan ser creados y pueden llamarse directamente a través de la clase;
2) El patrón Singleton tiene una mayor flexibilidad, y los métodos pueden ser anulados, porque las clases estáticas son todos métodos estáticos, por lo que no pueden anularse;
3) Si es un objeto muy pesado, el patrón Singleton puede ser perezoso para cargar, pero las clases estáticas no pueden hacerlo;
Entonces, se deben usar clases estáticas y ¿cuándo deberíamos usar el modo singleton? En primer lugar, si solo desea utilizar algunos métodos de herramientas, es mejor usar clases estáticas. Las analogías estáticas son más rápidas que las clases de singleton, porque la unión estática se realiza durante el período de compilación. Si desea mantener la información de estado o los recursos de acceso, debe usar el modo Singleton. También se puede decir que cuando necesita capacidades orientadas a objetos (como herencia, polimorfismo), elija clases de singleton y cuando solo proporciona algunos métodos, elija clases estáticas.
3. Cómo implementar el modo singleton
1. Modo hombre hambriento
El llamado modo hambriento es cargarse de inmediato. En general, se han generado instancias antes de llamar al método GetInstancef, lo que significa que se ha generado cuando la clase se carga. La desventaja de este modelo es muy obvia, que es que ocupa recursos. Cuando la clase Singleton es grande, en realidad queremos usarla y luego generar instancias. Por lo tanto, este método es adecuado para clases que ocupan menos recursos y se utilizarán durante la inicialización.
clase SingletonHungary {private senchetonhungary singletonhungary = new SingletonHungary (); // Establezca el constructor en privado para prohibir la instanciación a través de nuevos singletonhungary privado () {} public static singletonhungary getInstance () {return singletonhungary; }}2. Modo perezoso
El modo perezoso es una carga perezosa, también llamada carga perezosa. Cree una instancia en la que el programa sea necesario, para que la memoria no se desperdicie. Para el modo perezoso, aquí hay 5 métodos de implementación. Algunos métodos de implementación son la inicio de hilo, lo que significa que los problemas de sincronización de recursos pueden ocurrir en un entorno de concurrencia múltiple.
En primer lugar, el primer método es que no hay problema en un solo hilo, pero habrá problemas en el subproceso múltiple.
// Implementación perezosa del modo singleton 1 Presupado Inseguro clase Singletonlazy1 {private static singletonlazy1 singletonlazy; privado singletonlazy1 () {} public static singletonlazy1 getInstance () {if (null == Singletonlazy) {try {// simula cierta preparación antes de crear el objeto Thread.sleep (1000); } catch (InterruptedException e) {E.PrintStackTrace (); } singletonlazy = new Singletonlazy1 (); } return Singletonlazy; }}Simulemos 10 hilos asincrónicos para probar:
public class SingletonlazyTest {public static void main (string [] args) {thread2 [] Threadarr = new Thread2 [10]; for (int i = 0; i <thareR.length; i ++) {Thregarr [i] = new Thread2 (); Threadarr [i] .Start (); }}} // Test Thread Class Thread2 extiende el hilo {@Override public void run () {System.out.println (Singletonlazy1.getInstance (). HashCode ()); }}Resultados de ejecución:
124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303
Puede ver que sus hashcodes no son todos los mismos, lo que significa que se generan múltiples objetos en un entorno de múltiples subprocesos, que no cumple con los requisitos del patrón Singleton.
Entonces, ¿cómo hacer que el hilo sea seguro? En el segundo método, utilizamos la palabra clave sincronizada para sincronizar el método GetInstance.
// Singleton Mode Lazy Implementation 2-Seguridad // Al establecer el método de sincronización, la eficiencia es demasiado baja, todo el método es una clase bloqueada Singletonlazy2 {private static singletonlazy2 singletonlazy; privado singletonlazy2 () {} public static sincronizado sincronizado sincronizado singletonlazy2 getInstance () {try {if (null == singletonlazy) {// simula cierta preparación antes de crear el objeto Thread.sleep (1000); singletonlazy = new Singletonlazy2 (); }} catch (InterruptedException e) {E.PrintStackTrace (); } return Singletonlazy; }}Usando la clase de prueba anterior, los resultados de la prueba:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
Como se puede ver, este método logra la seguridad de los subprocesos. Sin embargo, la desventaja es que la eficiencia es demasiado baja y se ejecuta sincrónicamente. Si el siguiente hilo quiere obtener el objeto, debe esperar a que el hilo anterior se libere antes de que pueda continuar ejecutándose.
Entonces no podemos bloquear el método, sino bloquear el código adentro, lo que también puede lograr la seguridad de los subprocesos. Pero este método es el mismo que el método de sincronización, y también se ejecuta sincrónicamente y tiene muy baja eficiencia.
// Singletonlazy Implementación 3-Seguridad de hilo // mediante la configuración de bloques de código sincrónicos, la eficiencia es demasiado baja y todo el bloque de código está bloqueado de clase Singletonlazy3 {private static singletonlazy3 singletonlazy; private singletonlazy3 () {} public static singletonlazy3 getInStance () {try {sincronizado (singletonlazy3.class) {if (null == singletonlazy) {// simular hacer alguna preparación antes de crear un objeto thread.slele (1000); singletonlazy = new Singletonlazy3 (); }}} Catch (InterruptedException e) {// tODO: manejar excepción} return singletonlazy; }}Continuemos optimizando el código. Solo bloqueamos el código que crea el objeto, pero ¿puede esto garantizar la seguridad de los subprocesos?
// La implementación perezosa del modo Singleton 4-Supremo inseguro // Solo el código que crea instancias se sincroniza estableciendo bloques de código de sincronización // todavía hay problemas de seguridad de subprocesos Singletonlazy4 {private singletonlazy4 singletonlazy; private singletonlazy4 () {} public static singletonlazy4 getInStance () {try {if (null == Singletonlazy) {// código 1 // Simulación para hacer alguna preparación antes de crear un objeto Thread.sleep (1000); sincronizado (singletonlazy4.class) {singletonlazy = new Singletonlazy4 (); // Código 2}}} Catch (InterruptedException e) {// tODO: manejar excepción} return singletonlazy; }}Echemos un vistazo a los resultados de la ejecución:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
A juzgar por los resultados, este método no puede garantizar la seguridad del hilo. ¿Por qué? Supongamos que dos hilos A y B van al 'Código 1' al mismo tiempo, porque el objeto todavía está vacío en este momento, por lo que ambos pueden ingresar el método. Presente primero agarra la cerradura y crea el objeto. Después de que el hilo B obtenga el bloqueo, también irá al 'Código 2' cuando se libera, y se crea un objeto. Por lo tanto, un singleton no puede garantizarse en un entorno múltiple.
Sigamos optimizando. Dado que hay un problema con el método anterior, podemos hacer un juicio nulo en el bloque de código de sincronización. Este método es nuestro mecanismo de bloqueo de doble verificación DCL.
// El hombre liso en el modo Singleton implementa la seguridad de 5 hilos // al establecer el bloque de código de sincronización, use el mecanismo de bloqueo de doble verificación DCL // usando el mecanismo de bloqueo de doble verificación resuelve con éxito la inseguridad de los subprocesos y problemas de eficiencia implementados por el hombre perezoso en modo singleton // DCL también es una solución utilizada por la mayoría de la lectura multicial combinada con el modo singleton // la función de la función de si el primer juicio de si el juicio de si el juicio de si es un juicio de la actualidad. Cuando se crea el objeto Singletonlazy5, cuando se obtiene el objeto Singletonlazy5, no es necesario verificar el bloqueo del bloque de código de sincronización y el código posterior, y devolver directamente el objeto Singletonlazy5 // la función del segundo juicio: para resolver los problemas de seguridad en MultithReating, es decir, para garantizar la infracción del objeto. clase Singletonlazy5 {privado estático volátil senchletonlazy5 singletonlazy; privado singletonlazy5 () {} public static singletonlazy5 getInstance () {try {if (null == singletonlazy) {// simula cierta preparación antes de crear el objeto Thread.sleep (1000); sincronizado (singletonlazy5.class) {if (null == singletonlazy) {singletonlazy = new Singletonlazy5 (); }}}} capt (interruptedException e) {} return singletonlazy; }}Resultados de ejecución:
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
Podemos ver que el mecanismo de bloqueo de doble verificación de DCL resuelve los problemas de eficiencia y seguridad del rosca del modo de carga de carga perezosa. Este es también el método que usamos con más frecuencia.
palabra clave volátil
Aquí noté que al definir Singletonlazy, se usa la palabra clave volátil. Esto es para evitar que las instrucciones reordenen. ¿Por qué necesitamos hacer esto? Echemos un vistazo a un escenario:
El código va a Singletonlazy = new Singletonlazy5 (); Parece ser una oración, pero esta no es una operación atómica (o todos se ejecutan o no se ejecutan, y la mitad no se puede ejecutar). Esta oración se compila en 8 instrucciones de ensamblaje, y se hacen aproximadamente 3 cosas:
1. Asignar memoria a la instancia de Singletonlazy5.
2. Inicializar el constructor de Singletonlazy5
3. Apunte el objeto Singletonlazy al espacio de memoria asignado (tenga en cuenta que esta instancia no es nula).
Dado que el compilador Java permite que el procesador ejecute fuera de pedido (fuera de orden), y el orden de la memoria caché, los registros a la redacción de la memoria principal en JMM (Java Memory Medel) antes de JDK1.5, no se puede garantizar el orden del segundo y tercer punto anterior. Es decir, la orden de ejecución puede ser 1-2-3 o 1-3-2. Si es el último, y antes de que se ejecute 3 y 2 no se ejecute, se cambia a Hilo 2. En este momento, Singletonlazy ya ha ejecutado el tercer punto en el hilo uno, Singletonlazy ya no está vacío, por lo que el hilo dos directamente toma Singletonlazy, luego lo usa, y luego informa naturalmente un error. Además, este tipo de error que es difícil de rastrear y difícil de reproducir no puede encontrarse en la última semana de depuración.
El método de escritura de DCL para implementar singletons se recomienda en muchos libros técnicos y libros de texto (incluidos libros basados en versiones anteriores de JDK1.4), pero en realidad no es completamente correcto. De hecho, DCL es factible en algunos idiomas (como C), dependiendo de si se puede garantizar el orden de 2 y 3 pasos. Después de JDK1.5, el funcionario ha notado este problema, por lo que el JMM se ha ajustado y la palabra clave volátil se ha concreto. Por lo tanto, si JDK es una versión de 1.5 o posterior, solo necesita agregar la palabra clave volátil a la definición de Singletonlazy, lo que puede garantizar que Singletonlazy se lea desde la memoria principal cada vez, y se puede prohibir la reordenamiento, y puede usar el método de escritura DCL para completar el modo Singleton. Por supuesto, Volatile afectará más o menos el rendimiento. Lo más importante es que también debemos considerar JDK1.42 y versiones anteriores, por lo que la mejora de la escritura de patrones singleton aún continúa.
3. Clase interna estática
Según las consideraciones anteriores, podemos usar clases internos estáticas para implementar el patrón Singleton, el código es el siguiente:
// Implementar el modo Singleton con clases internos estáticas: la clase de seguridad Singletonstaticinner {private SingletonstaticInner () {} private static class SingletonInner {private static singletonstaticinner singletonstaticinner = new Singletonstaticinner (); } public static singletonstaticinner getInStance () {try {Thread.sleep (1000); } Catch (InterruptedException e) {// TODO Auto Generado Bloque E.PrintStackTrace (); } return singletoninner.singletonstaticinner; }}Se puede ver que no realizamos explícitamente ninguna operación de sincronización de esta manera, entonces, ¿cómo garantiza la seguridad de los subprocesos? Al igual que el modo Hungry Man, es una característica que JVM asegura que los miembros estáticos de la clase solo se puedan cargar una vez, de modo que solo haya un objeto de instancia desde el nivel JVM. Entonces, la pregunta es, ¿cuál es la diferencia entre este método y el modelo de hombre hambriento? ¿No se está cargando de inmediato? De hecho, cuando se carga una clase, su clase interna no se cargará al mismo tiempo. Se carga una clase, que ocurre cuando y solo si se llama a uno de sus miembros estáticos (dominios estáticos, constructores, métodos estáticos, etc.).
Se puede decir que este método es la solución óptima para implementar el patrón Singleton.
4. Bloques de código estático
Aquí hay un patrón de implementación de bloques de código estático. Este método es similar al primero, y también es un modelo de hombre hambriento.
// Use bloques de código estáticos para implementar la clase Singleton Mode SingletonstaticBlock {private static singletonstaticblock singletonstaticblock; static {SingletonstaticBlock = new SingletonstaticBlock (); } public static singletonstaticblock getInstance () {return singletonstaticblock; }}5. Serialización y deserialización
¿Por qué LZ recomienda serialización y deserialización? Debido a que aunque el modo singleton puede garantizar la seguridad de los subprocesos, se generarán múltiples objetos en el caso de serialización y deserialización. Ejecute la siguiente clase de prueba,
public class SingletonstaticInserializeTest {public static void main (string [] args) {try {singletonstaticinnerserialize serialize = singletonstaticinnerserialize.getInstance (); System.out.println (serialize.hashcode ()); // Serializar fileOutputStream fo = new FileOutputStream ("Tem"); ObjectOutputStream oo = new ObjectOutputStream (fo); oo.writeObject (serialize); oo.close (); fo.close (); // deserializar fileInputStream fi = new FileInputStream ("Tem"); ObjectInputStream oi = new ObjectInputStream (fi); Singletonstaticinnerserialize serialize2 = (singletonstaticinnerserialize) oi.readObject (); oi.close (); fi.close (); System.out.println (serialize2.hashcode ()); } catch (Exception e) {E.PrintStackTrace (); }}} // Use clases internas anónimas para implementar el patrón Singleton. Al encontrar la serialización y la deserialización, no se obtiene la misma instancia. Class SingletOnstaticInserialize implementos serializables { / *** 28 de marzo de 2018* / privado estático final Long SerialVersionUid = 1l; Clase estática privada Innclass {private static singletonstaticinnerserialize singletonstaticinnerserialize = new SingletonstaticInnerserialize (); } public static singletonstaticinnerserialize getInstance () {return innerClass.singletonstaticInnerserialize; } // Objeto protegido ReadResolve () {// System.out.println ("El método ReadResolve se llamó"); // return InnClass.singletonstaticInnerserialize; //}}Puedes ver:
865113938
1078694789
Los resultados muestran que de hecho son dos instancias de objetos diferentes que violan el patrón Singleton. Entonces, ¿cómo resolver este problema? La solución es usar el método ReadResolve () en deserialización, eliminar el código de comentarios anterior y ejecutarlo nuevamente:
865113938
Se llamó al método readresolve
865113938
La pregunta es, ¿quién es la forma sagrada del método Readresolve ()? De hecho, cuando el JVM deserializa y "ensambla" un nuevo objeto a partir de la memoria, llamará automáticamente a este método ReadResolve para devolver el objeto que especificamos, y las reglas Singleton están garantizadas. La aparición de readResolve () permite a los programadores controlar los objetos obtenidos a través de la deserialización por su cuenta.
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.