Singletons em Java
1. Introdução
Neste artigo rápido, discutiremos as duas maneiras mais populares de implementar Singletons em Java puro.
2. Singleton baseado em classe
A abordagem mais popular é implementar um Singleton criando uma classe regular e garantindo que ele tenha:
-
Um construtor privado
-
Um campo estático contendo sua única instância
-
Um método estático de fábrica para obter a instância
Também adicionaremos uma propriedade info, apenas para uso posterior. Portanto, nossa implementação ficará assim:
public final class ClassSingleton {
private static ClassSingleton INSTANCE;
private String info = "Initial info class";
private ClassSingleton() {
}
public static ClassSingleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
// getters and setters
}
Embora essa seja uma abordagem comum, é importante observar que écan be problematic in multithreading scenarios, que é a principal razão para usar Singletons.
Simplificando, pode resultar em mais de uma instância, quebrando o princípio básico do padrão. Embora existam soluções de bloqueio para esse problema, nossa próxima abordagem resolve esses problemas no nível raiz.
3. Enum Singleton
Avançando, não vamos discutir outra abordagem interessante - que é usar enumerações:
public enum EnumSingleton {
INSTANCE("Initial class info");
private String info;
private EnumSingleton(String info) {
this.info = info;
}
public EnumSingleton getInstance() {
return INSTANCE;
}
// getters and setters
}
Essa abordagem possui serialização e segurança de encadeamento garantidas pela própria implementação do enum, o que garante internamente que apenas uma única instância esteja disponível, corrigindo os problemas apontados na implementação baseada em classe.
4. Uso
Para usar nossoClassSingleton, simplesmente precisamos obter a instância estaticamente:
ClassSingleton classSingleton1 = ClassSingleton.getInstance();
System.out.println(classSingleton1.getInfo()); //Initial class info
ClassSingleton classSingleton2 = ClassSingleton.getInstance();
classSingleton2.setInfo("New class info");
System.out.println(classSingleton1.getInfo()); //New class info
System.out.println(classSingleton2.getInfo()); //New class info
Quanto aoEnumSingleton, podemos usá-lo como qualquer outro Java Enum:
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();
System.out.println(enumSingleton1.getInfo()); //Initial enum info
EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New enum info");
System.out.println(enumSingleton1.getInfo()); // New enum info
System.out.println(enumSingleton2.getInfo()); // New enum info
5. Armadilhas comuns
Singleton é um padrão de design enganosamente simples e existem poucos erros comuns que um programador pode cometer ao criar um singleton.
Distinguimos dois tipos de problemas com singletons:
-
existencial (precisamos de um singleton?)
-
implementacional (implementamos corretamente?)
5.1. Questões Existenciais
Conceitualmente, um singleton é um tipo de variável global. Em geral, sabemos que variáveis globais devem ser evitadas - especialmente se seus estados são mutáveis.
Não estamos dizendo que nunca devemos usar singletons. No entanto, estamos dizendo que pode haver maneiras mais eficientes de organizar nosso código.
Se a implementação de um método depende de um objeto singleton, por que não passá-lo como um parâmetro? Nesse caso, mostramos explicitamente do que o método depende. Como conseqüência, podemos facilmente zombar dessas dependências (se necessário) ao executar o teste.
Por exemplo, singletons são frequentemente usados para abranger os dados de configuração do aplicativo (ou seja, conexão com o repositório). Se eles forem usados como objetos globais, fica difícil escolher a configuração para o ambiente de teste.
Portanto, quando executamos os testes, o banco de dados de produção fica estragado com os dados de teste, o que dificilmente é aceitável.
Se precisarmos de um singleton, podemos considerar a possibilidade de delegar sua instanciação a outra classe - uma espécie de fábrica - que deve garantir que haja apenas uma instância do singleton em jogo.
5.2. Questões de implementação
Embora os singletons pareçam bastante simples, suas implementações podem sofrer de vários problemas. Tudo resulta no fato de que podemos ter mais do que apenas uma instância da classe.
Synchronization A implementação com um construtor privado que apresentamos acima não é segura para thread: ela funciona bem em um ambiente de thread único, mas em um multi-thread, devemos usar a técnica de sincronização para garantir a atomicidade de a operação:
public synchronized static ClassSingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
Observe a palavra-chavesynchronized na declaração do método. O corpo do método tem várias operações (comparação, instanciação e retorno).
Na ausência de sincronização, existe a possibilidade de que dois threads intercalem suas execuções de tal forma que a expressãoINSTANCE == null avalia comotrue para ambos os threads e, como resultado, duas instâncias deClassSingleton seja criado.
Synchronization pode afetar significativamente o desempenho. Se este código for invocado frequentemente, devemos acelerá-lo usando várias técnicas comolazy initialization oudouble-checked locking (esteja ciente de que isso pode não funcionar como esperado devido às otimizações do compilador). Podemos ver mais detalhes em nosso tutorial “https://www.example.com/java-singleton-double-checked-locking[Double-Checked Locking with Singleton]“.
Multiple Instances Existem vários outros problemas com os singletons relacionados à própria JVM que podem nos fazer terminar com várias instâncias de um singleton. Esses problemas são bastante sutis e daremos uma breve descrição de cada um deles:
-
Um singleton deve ser exclusivo por JVM. Isso pode ser um problema para sistemas distribuídos ou sistemas cujos internos são baseados em tecnologias distribuídas.
-
Todo carregador de classes pode carregar sua versão do singleton.
-
Um singleton pode ser coletado como lixo uma vez que ninguém tenha uma referência a ele. Esse problema não leva à presença de várias instâncias singleton por vez, mas quando recriada, a instância pode ser diferente da versão anterior.
6. Conclusão
Neste tutorial rápido, nos concentramos em como implementar o padrão Singleton usando apenas o núcleo do Java e como ter certeza de que é consistente e como fazer uso dessas implementações.
A implementação completa desses exemplos pode ser encontradaover on GitHub.