Bloqueio verificado duas vezes com Singleton
1. Introdução
Neste tutorial, vamos falar sobre o padrão de design de bloqueio verificado duas vezes. Esse padrão reduz o número de aquisições de bloqueio, basta verificar previamente a condição de bloqueio. Como resultado disso, geralmente há um aumento de desempenho.
Vamos dar uma olhada em como isso funciona.
2. Implementação
Para começar, vamos considerar um singleton simples com sincronização draconiana:
public class DraconianSingleton {
private static DraconianSingleton instance;
public static synchronized DraconianSingleton getInstance() {
if (instance == null) {
instance = new DraconianSingleton();
}
return instance;
}
// private constructor and other methods ...
}
Apesar de esta classe ser thread-safe, podemos ver que há uma desvantagem de desempenho clara: cada vez que queremos obter a instância de nosso singleton, precisamos adquirir um bloqueio potencialmente desnecessário.
Para corrigir isso,we could instead start by verifying if we need to create the object in the first place and only in that case we would acquire the lock.
Indo além, queremos executar a mesma verificação assim que entrarmos no bloco sincronizado, a fim de manter a operação atômica:
public class DclSingleton {
private static volatile DclSingleton instance;
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton .class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
// private constructor and other methods...
}
Uma coisa a ter em mente com esse padrão é quethe field needs to be volatile para evitar problemas de incoerência do cache. De fato, o modelo de memória Java permite a publicação de objetos parcialmente inicializados e isso pode levar a erros sutis.
3. Alternativas
Embora o bloqueio com verificação dupla possa potencialmente acelerar as coisas, ele tem pelo menos dois problemas:
-
uma vez que requer a palavra-chavevolatile para funcionar corretamente, não é compatível com Java 1.4 e versões anteriores
-
é bastante prolixo e torna o código difícil de ler
Por essas razões, vamos examinar algumas outras opções sem essas falhas. Todos os métodos a seguir delegam a tarefa de sincronização para a JVM.
3.1. Inicialização Antecipada
A maneira mais fácil de obter segurança de encadeamento é alinhar a criação do objeto ou usar um bloco estático equivalente. Isso tira vantagem do fato de que os campos e blocos estáticos são inicializados um após o outro (Java Language Specification 12.4.2):
public class EarlyInitSingleton {
private static final EarlyInitSingleton INSTANCE = new EarlyInitSingleton();
public static EarlyInitSingleton getInstance() {
return INSTANCE;
}
// private constructor and other methods...
}
3.2. Inicialização sob demanda
Além disso, como sabemos pela referência do Java Language Specification no parágrafo anterior que ocorre uma inicialização de classe na primeira vez em que usamos um de seus métodos ou campos, podemos usar uma classe estática aninhada para implementar a inicialização lenta:
public class InitOnDemandSingleton {
private static class InstanceHolder {
private static final InitOnDemandSingleton INSTANCE = new InitOnDemandSingleton();
}
public static InitOnDemandSingleton getInstance() {
return InstanceHolder.INSTANCE;
}
// private constructor and other methods...
}
Neste caso, a classeInstanceHolder atribuirá o campo na primeira vez que o acessarmos invocandogetInstance.
3.3. Enum Singleton
A última solução vem do livroEffective Java (Item 3) de Joshua Block e usaenum em vez declass. No momento da escrita, essa é considerada a maneira mais concisa e segura de escrever um singleton:
public enum EnumSingleton {
INSTANCE;
// other methods...
}
4. Conclusão
Para resumir, este artigo rápido analisou o padrão de bloqueio verificado novamente, seus limites e algumas alternativas.
Na prática, a excessiva verbosidade e a falta de compatibilidade com versões anteriores tornam esse padrão propenso a erros e, portanto, devemos evitá-lo. Em vez disso, devemos usar uma alternativa que permita à JVM sincronizar.
Como sempre, o código de todos os exemplos éavailable on GitHub.