Implementação do Padrão Java Singleton
Para implementar um padrão singleton, temos diferentes abordagens, mas todas elas têm os seguintes conceitos comuns.
- Construtor privado para restringir a instanciação da classe de outras classes.
- Variável estática privada da mesma classe que é a única instância da classe.
- Método estático público que retorna a instância da classe. Este é o ponto de acesso global para o mundo externo obter a instância da classe singleton.
Nas próximas seções, aprenderemos diferentes abordagens para implementação de padrões singleton e preocupações de design com a implementação.
1. Inicialização rápida
Na inicialização ansiosa, a instância da classe singleton é criada no momento do carregamento da classe. A desvantagem da inicialização ansiosa é que o método é criado mesmo que o aplicativo cliente não o esteja usando. Aqui está a implementação da classe singleton de inicialização estática:
Se sua classe singleton não estiver usando muitos recursos, essa é a abordagem a ser usada. Mas na maioria dos cenários, classes singleton são criadas para recursos como Sistema de Arquivos, conexões de Banco de Dados, etc. Devemos evitar a instanciação, a menos que o cliente chame o getInstance
método. Além disso, esse método não fornece nenhuma opção para tratamento de exceções.
2. Inicialização de bloco estático
A implementação da inicialização de bloco estático é semelhante à inicialização ansiosa, exceto que a instância da classe é criada no bloco estático que fornece a opção de tratamento de exceções .
Tanto a inicialização rápida quanto a inicialização de bloco estático criam a instância antes mesmo que ela seja usada, e essa não é a melhor prática a ser usada.
3. Inicialização preguiçosa
O método de inicialização lazy para implementar o padrão singleton cria a instância no método de acesso global. Aqui está o código de exemplo para criar a classe singleton com esta abordagem:
A implementação anterior funciona bem no caso do ambiente single-threaded, mas quando se trata de sistemas multi-threaded, pode causar problemas se vários threads estiverem dentro da if
condição ao mesmo tempo. Isso destruirá o padrão singleton e ambos os threads obterão instâncias diferentes da classe singleton. Na próxima seção, veremos diferentes maneiras de criar uma classe singleton thread-safe .
4. Singleton seguro para threads
Uma maneira simples de criar uma classe singleton thread-safe é fazer com que o método de acesso global seja sincronizado para que apenas uma thread possa executar esse método por vez. Aqui está uma implementação geral dessa abordagem:
A implementação anterior funciona bem e fornece segurança de thread, mas reduz o desempenho devido ao custo associado ao método synchronized, embora precisemos dele apenas para os primeiros threads que podem criar instâncias separadas. Para evitar essa sobrecarga extra toda vez, o princípio de bloqueio verificado duas vezes é usado. Nessa abordagem, o bloco synchronized é usado dentro da if
condição com uma verificação adicional para garantir que apenas uma instância de uma classe singleton seja criada. O seguinte trecho de código fornece a implementação de bloqueio verificado duas vezes:
Continue seu aprendizado com a classe Thread Safe Singleton .
5. Implementação de Bill Pugh Singleton
Antes do Java 5, o modelo de memória Java tinha muitos problemas, e as abordagens anteriores costumavam falhar em certos cenários em que muitos threads tentavam obter a instância da classe singleton simultaneamente. Então Bill Pugh surgiu com uma abordagem diferente para criar a classe singleton usando uma classe auxiliar estática interna . Aqui está um exemplo da implementação do Singleton de Bill Pugh:
Observe a classe estática interna privada que contém a instância da classe singleton. Quando a classe singleton é carregada, SingletonHelper
a classe não é carregada na memória e somente quando alguém chama o getInstance()
método, essa classe é carregada e cria a instância da classe singleton. Essa é a abordagem mais amplamente usada para a classe singleton, pois não requer sincronização.
6. Usando Reflexão para destruir o Padrão Singleton
A reflexão pode ser usada para destruir todas as abordagens de implementação singleton anteriores. Aqui está uma classe de exemplo:
Ao executar a classe de teste anterior, você notará que hashCode
ambas as instâncias não são as mesmas, o que destrói o padrão singleton. O Reflection é muito poderoso e usado em muitos frameworks como Spring e Hibernate. Continue seu aprendizado com o Tutorial Java Reflection .
7. Enumeração Singleton
Para superar essa situação com o Reflection, Joshua Bloch sugere o uso de enum
para implementar o padrão de design singleton, já que Java garante que qualquer enum
valor seja instanciado apenas uma vez em um programa Java. Como os valores Java Enum são globalmente acessíveis, o singleton também é. A desvantagem é que o enum
tipo é um tanto inflexível (por exemplo, ele não permite inicialização preguiçosa).
8. Serialização e Singleton
Às vezes, em sistemas distribuídos, precisamos implementar Serializable
interface na classe singleton para que possamos armazenar seu estado no sistema de arquivos e recuperá-lo em um momento posterior. Aqui está uma pequena classe singleton que implementa Serializable
interface também:
O problema com a classe singleton serializada é que sempre que a desserializamos, ela criará uma nova instância da classe. Aqui está um exemplo:
Esse código produz esta saída:
instanceOne hashCode=2011117821
instanceTwo hashCode=109647522
Então ele destrói o padrão singleton. Para superar esse cenário, tudo o que precisamos fazer é fornecer a implementação do readResolve()
método.
Depois disso, você notará que hashCode
ambas as instâncias são iguais no programa de teste.
Fonte: https://www.digitalocean.com/community/tutorials/java-singleton-design-pattern-best-practices-examples