1. Aula singleton estilo faminto
}
instância Singleton estática privada = new Singleton();
singleton estático privado getInstance(){
instância de retorno;
}
}
Recursos: instanciação antecipada do estilo Hungry, não há problema de multi-threading no estilo preguiçoso, mas não importa se chamamos getInstance() ou não, haverá uma instância na memória
2. Classe singleton de classe interna
}
classe privada SingletonHoledr(){
instância Singleton estática privada = new Singleton();
}
singleton estático privado getInstance(){
retornar SingletonHoledr.instance;
}
}
Recursos: Na classe interna, o carregamento lento é implementado Somente quando chamamos getInstance() uma instância única será criada na memória. Ele também resolve o problema de multithreading no estilo lento. .
3. Aula preguiçosa de solteiro
}
instância Singleton estática privada;
public static Singleton getInstance(){
if(instância == nulo){
retornar instância = new Singleton();
}outro{
instância de retorno;
}
}
}
Características: No estilo preguiçoso, existem threads A e B. Quando o thread A é executado para a linha 8, ele salta para o thread B. Quando B também é executado para a linha 8, as instâncias de ambos os threads estão vazias, então ele irá gerar dois exemplos . A solução é sincronizar:
A sincronização é possível, mas não eficiente:
}
instância Singleton estática privada;
público estático sincronizado Singleton getInstance(){
if(instância == nulo){
retornar instância = new Singleton();
}outro{
instância de retorno;
}
}
}
Não haverá erro ao escrever um programa como este, porque todo o getInstance é uma "seção crítica" inteira, mas a eficiência é muito baixa, porque nosso objetivo é na verdade apenas bloquear ao inicializar a instância pela primeira vez, e então obtenha a instância Ao usar, não há necessidade de sincronização de thread.
Então, pessoas inteligentes criaram a seguinte abordagem:
Método de escrita de bloqueio de verificação dupla:
public static Singleton getSingle(){ //Objetos externos podem ser obtidos através deste método
if(único == nulo){
sincronizado (Singleton.class) { //Garante que apenas um objeto possa acessar este bloco sincronizado ao mesmo tempo
if(único == nulo){
único = novo Singleton();
}
}
}
return single; //Retorna o objeto criado
}
}
A ideia é muito simples, ou seja, precisamos apenas sincronizar (sincronizar) a parte do código que inicializa a instância para que o código fique correto e eficiente.
Este é o chamado mecanismo de "bloqueio de verificação dupla" (como o nome sugere).
Infelizmente, essa forma de escrever está errada em muitas plataformas e na otimização de compiladores.
A razão é que o comportamento da linha de código instance = new Singleton() em diferentes compiladores é imprevisível. Um compilador otimizador pode implementar legalmente instance = new Singleton() da seguinte forma:
1. instância = alocar memória para a nova entidade
2. Chame o construtor Singleton para inicializar as variáveis de membro da instância
Agora imagine que os threads A e B estão chamando getInstance. O thread A entra primeiro e é expulso da CPU quando a etapa 1 é executada. Então o thread B entra, e o que B vê é que a instância não é mais nula (a memória foi alocada), então ele começa a usar a instância com confiança, mas isso está errado, porque neste momento, as variáveis membro da instância ainda são padrão valor, A ainda não teve tempo de executar a etapa 2 para concluir a inicialização da instância.
Claro, o compilador também pode implementá-lo assim:
1. temp = alocar memória
2. Chame o construtor de temp
3. instância = temp.
Se o compilador se comporta assim, parece que não temos problema, mas o fato não é tão simples, pois não podemos saber como determinado compilador faz isso, pois esse problema não está definido no modelo de memória do Java.
O bloqueio de verificação dupla é aplicável a tipos básicos (como int). Obviamente, porque o tipo básico não chama o construtor.