1. Clase singleton estilo hambriento
}
instancia privada estática Singleton = new Singleton();
getInstance () Singleton estático privado
instancia de devolución;
}
}
Características: creación de instancias de estilo hambriento por adelantado, no hay problemas de subprocesos múltiples en estilo diferido, pero no importa si llamamos a getInstance () o no, habrá una instancia en la memoria.
2. Clase singleton de clase interna
}
clase privada SingletonHoledr(){
instancia privada estática Singleton = new Singleton();
}
getInstance () Singleton estático privado
devolver instancia SingletonHoledr;
}
}
Características: En la clase interna, se implementa la carga diferida. Solo cuando llamamos a getInstance() se creará una instancia única en la memoria. También resuelve el problema de subprocesos múltiples en el estilo diferido. .
3. Clase Singleton perezosa
}
instancia privada estática Singleton;
getInstance público estático Singleton(){
si (instancia == nulo) {
instancia de retorno = nuevo Singleton();
}demás{
instancia de devolución;
}
}
}
Características: en el estilo perezoso, hay subprocesos A y B. Cuando el subproceso A llega a la línea 8, salta al subproceso B. Cuando B también corre a la línea 8, las instancias de ambos subprocesos están vacías, por lo que generará dos ejemplos. . La solución es sincronizar:
La sincronización es posible pero no eficiente:
}
instancia privada estática Singleton;
singleton estático público sincronizado getInstance(){
si (instancia == nulo) {
instancia de retorno = nuevo Singleton();
}demás{
instancia de devolución;
}
}
}
No habrá ningún error al escribir un programa como este, porque todo getInstance es una "sección crítica" completa, pero la eficiencia es muy pobre, porque nuestro propósito en realidad es solo bloquear cuando inicializamos la instancia por primera vez, y luego obtenga la instancia cuando se utiliza, no es necesaria ninguna sincronización de subprocesos.
Entonces a las personas inteligentes se les ocurrió el siguiente enfoque:
Método de escritura de bloqueo de doble verificación:
public static Singleton getSingle(){ // Los objetos externos se pueden obtener a través de este método
si(único == nulo){
sincronizado (Singleton.class) { // Garantiza que solo un objeto pueda acceder a este bloque sincronizado al mismo tiempo
si(único == nulo){
único = nuevo Singleton();
}
}
}
return single; //Devuelve el objeto creado
}
}
La idea es muy simple, es decir, solo necesitamos sincronizar (sincronizar) la parte del código que inicializa la instancia para que el código sea correcto y eficiente.
Este es el llamado mecanismo de "bloqueo de doble verificación" (como su nombre indica).
Desafortunadamente, esta forma de escribir es incorrecta en muchas plataformas y optimizadores de compiladores.
La razón es que el comportamiento de la línea de código instancia = new Singleton() en diferentes compiladores es impredecible. Un compilador de optimización puede implementar legalmente instancia = new Singleton() de la siguiente manera:
1. instancia = asignar memoria a la nueva entidad
2. Llame al constructor Singleton para inicializar las variables miembro de la instancia.
Ahora imagine que los subprocesos A y B están llamando a getInstance. El subproceso A ingresa primero y es expulsado de la CPU cuando se ejecuta el paso 1. Luego ingresa el hilo B, y lo que B ve es que la instancia ya no es nula (se ha asignado memoria), por lo que comienza a usar la instancia con confianza, pero esto está mal, porque en este momento, las variables miembro de la instancia todavía son predeterminadas. valor, A aún no ha tenido tiempo de ejecutar el paso 2 para completar la inicialización de la instancia.
Por supuesto, el compilador también puede implementarlo así:
1. temp = asignar memoria
2. Llame al constructor de temp.
3. instancia = temporal
Si el compilador se comporta así, parece que no tenemos ningún problema, pero el hecho no es tan simple, porque no podemos saber cómo lo hace un determinado compilador, porque este problema no está definido en el modelo de memoria de Java.
El bloqueo de doble verificación es aplicable a tipos básicos (como int). Obviamente, porque el tipo básico no llama al constructor.