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.
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.
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.
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 .
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 .
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.
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 .
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).
À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:
Output
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