O que é uma maneira eficiente de implementar um padrão singleton em Java?
java
singleton
design-patterns
Riyaz Mohammed Ibrahim
fonte
fonte
Respostas:
Use uma enumeração:
Joshua Bloch explicou essa abordagem em sua palestra Effective Java Reloaded no Google I / O 2008: link para vídeo . Veja também os slides 30 a 32 de sua apresentação ( effective_java_reloaded.pdf ):
Edit: Uma parte online do "Java Efetivo" diz:
fonte
Dependendo do uso, existem várias respostas "corretas".
Como o java5, a melhor maneira de fazer isso é usar um enum:
Antes do java5, o caso mais simples é:
Vamos revisar o código. Primeiro, você quer que a aula seja final. Nesse caso, usei a
final
palavra-chave para informar aos usuários que é final. Então você precisa tornar o construtor privado para impedir que os usuários criem seu próprio Foo. Lançar uma exceção do construtor impede que os usuários usem a reflexão para criar um segundo Foo. Em seguida, você cria umprivate static final Foo
campo para armazenar a única instância e umpublic static Foo getInstance()
método para devolvê-lo. A especificação Java garante que o construtor seja chamado apenas quando a classe for usada pela primeira vez.Quando você tem um objeto muito grande ou código de construção pesada E também tem outros métodos ou campos estáticos acessíveis que podem ser usados antes que uma instância seja necessária, então e somente então você precisa usar a inicialização lenta.
Você pode usar a
private static class
para carregar a instância. O código ficaria assim:Como a linha
private static final Foo INSTANCE = new Foo();
é executada apenas quando a classe FooLoader é realmente usada, isso cuida da instanciação lenta e é garantida a segurança de threads.Quando você também deseja serializar seu objeto, precisa garantir que a desserialização não crie uma cópia.
O método
readResolve()
garantirá que a única instância seja retornada, mesmo quando o objeto foi serializado em uma execução anterior do seu programa.fonte
Isenção de responsabilidade: Acabei de resumir todas as respostas impressionantes e as escrevi em minhas palavras.
Ao implementar o Singleton, temos 2 opções
1. Carregamento lento
2. Carregamento antecipado
O carregamento lento adiciona um pouco de sobrecarga (muito para ser honesto), portanto, use-o somente quando você tiver um objeto muito grande ou código de construção pesada E também tiver outros métodos ou campos estáticos acessíveis que possam ser usados antes que uma instância seja necessária, e somente então você precisará usar a inicialização lenta. Caso contrário, escolher o carregamento antecipado é uma boa escolha.
A maneira mais simples de implementar o Singleton é
Tudo é bom, exceto seu singleton carregado cedo. Vamos tentar o singleton carregado preguiçosamente
Até aí tudo bem, mas nosso herói não sobreviverá enquanto luta sozinho com vários tópicos malignos que desejam muitos exemplos de nosso herói. Então, vamos protegê-lo do mal multi-threading
mas não é o suficiente para proteger o herói, sério !!! É o melhor que podemos / devemos fazer para ajudar nosso herói
Isso é chamado de "idioma de bloqueio com verificação dupla". É fácil esquecer a afirmação volátil e difícil entender por que ela é necessária.
Para detalhes: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Agora temos certeza sobre o mal do fio, mas e a cruel serialização? Temos que garantir que, mesmo durante a desserialização, nenhum novo objeto seja criado
O método
readResolve()
garantirá que a única instância seja retornada, mesmo quando o objeto foi serializado em uma execução anterior do nosso programa.Finalmente, adicionamos proteção suficiente contra threads e serialização, mas nosso código parece volumoso e feio. Vamos dar uma olhada no nosso herói
Sim, este é o nosso herói mesmo :)
Como a linha
private static final Foo INSTANCE = new Foo();
é executada apenas quando a classeFooLoader
é realmente usada, isso cuida da instanciação lenta,e é garantido que seja seguro para threads.
E chegamos até aqui, aqui está a melhor maneira de alcançar tudo o que fizemos é a melhor maneira possível
Que internamente será tratado como
É isso aí! Não há mais medo de serialização, threads e código feio. Também ENUMS singleton são preguiçosamente inicializado .
-Joshua Bloch em "Java eficaz"
Agora você deve ter percebido por que os ENUMS são considerados a melhor maneira de implementar o Singleton e agradecemos sua paciência :)
Atualizei-o no meu blog .
fonte
serialVersionUID
de0L
. Terceiro problema: Sem personalização: Qualquer método writeObject, readObject, readObjectNoData, writeReplace e readResolve específico da classe definido por tipos de enumeração é ignorado durante a serialização e desserialização.A solução publicada por Stu Thompson é válida no Java5.0 e posterior. Mas eu preferiria não usá-lo porque acho que é propenso a erros.
É fácil esquecer a afirmação volátil e difícil entender por que ela é necessária. Sem o volátil, esse código não seria mais seguro para threads devido ao antipadrão de bloqueio verificado duas vezes. Veja mais sobre isso no parágrafo 16.2.4 da Concorrência Java na Prática . Resumindo: esse padrão (anterior ao Java5.0 ou sem a instrução volátil) pode retornar uma referência ao objeto Bar que está (ainda) em um estado incorreto.
Esse padrão foi inventado para otimização de desempenho. Mas isso realmente não é mais uma preocupação real. O seguinte código de inicialização lenta é rápido e, mais importante, fácil de ler.
fonte
getBar()
. (E segetBar
for chamado "muito cedo", você enfrentará o mesmo problema, independentemente de como os singleons sejam implementados.) Você pode ver o carregamento lento da classe do código acima aqui: pastebin.com/iq2eayiRSegmento de thread seguro em Java 5+:
EDIT : Preste atenção ao
volatile
modificador aqui. :) É importante porque sem ele, outros encadeamentos não são garantidos pelo JMM (Java Memory Model) para ver alterações em seu valor. A sincronização não cuida disso - apenas serializa o acesso a esse bloco de código.EDIT 2 : A resposta do @Bno detalha a abordagem recomendada por Bill Pugh (FindBugs) e é discutível melhor. Leia e vote também na resposta dele.
fonte
Esqueça a inicialização lenta , é muito problemática. Esta é a solução mais simples:
fonte
Certifique-se de que você realmente precisa. Faça um google para "antipadrão singleton" para ver alguns argumentos contra ele. Não há nada inerentemente errado com isso, suponho, mas é apenas um mecanismo para expor alguns recursos / dados globais, portanto, verifique se esse é o melhor caminho. Em particular, achei a injeção de dependência mais útil, principalmente se você também estiver usando testes de unidade porque o DI permite que você use recursos simulados para fins de teste.
fonte
Estou perplexo com algumas das respostas que sugerem a DI como uma alternativa ao uso de singletons; esses são conceitos não relacionados. Você pode usar o DI para injetar instâncias singleton ou não singleton (por exemplo, por thread). Pelo menos isso é verdade se você usa o Spring 2.x, não posso falar por outras estruturas de DI.
Portanto, minha resposta ao OP seria (com exceção do código de exemplo mais trivial):
Essa abordagem fornece uma boa arquitetura desacoplada (e, portanto, flexível e testável), na qual o uso de um singleton é um detalhe de implementação facilmente reversível (desde que todos os singletons usados sejam seguros para o segmento, é claro).
fonte
TicketNumberer
que precisa ter uma única instância global e onde você deseja escrever uma classeTicketIssuer
que contenha uma linha de códigoint ticketNumber = ticketNumberer.nextTicketNumber();
. No pensamento singleton tradicional, a linha de código anterior teria que ser algo parecidoTicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. No pensamento de DI, a classe teria um construtorpublic TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.main
método do aplicativo (ou um de seus subordinados) criaria a dependência e depois chamaria o construtor. Essencialmente, o uso de uma variável global (ou um método global) é apenas uma forma simples do temido padrão de localizador de serviço e pode ser substituído pela injeção de dependência, como qualquer outro uso desse padrão.Considere realmente por que você precisa de um singleton antes de escrevê-lo. Há um debate quase religioso sobre o uso deles, que você pode facilmente encontrar se pesquisar no Google singletons em Java.
Pessoalmente, tento evitar os singletons o mais rápido possível por várias razões, novamente a maioria dos quais pode ser encontrada nos singletons do Google. Sinto que muitas vezes os singletons são abusados porque são fáceis de entender por todos, são usados como um mecanismo para obter dados "globais" em um design OO e são usados porque é fácil contornar o gerenciamento do ciclo de vida do objeto (ou realmente pensando em como você pode fazer A de dentro de B). Veja coisas como Inversion of Control (IoC) ou Dependency Injection (DI) para obter um bom campo intermediário.
Se você realmente precisa de um, a wikipedia tem um bom exemplo de uma implementação adequada de um singleton.
fonte
A seguir, são apresentadas três abordagens diferentes
1) Enum
2) Travamento verificado / carregamento preguiçoso
3) Método estático de fábrica
fonte
Eu uso o Spring Framework para gerenciar meus singletons. Ele não impõe o "singleton-ness" da classe (o que você realmente não pode fazer de qualquer maneira se houver vários carregadores de classe envolvidos), mas fornece uma maneira muito fácil de construir e configurar diferentes fábricas para criar diferentes tipos de objetos.
fonte
A Wikipedia tem alguns exemplos de singletons, também em Java. A implementação do Java 5 parece bastante completa e é segura para threads (bloqueio verificado duas vezes).
fonte
Versão 1:
Carregamento preguiçoso, rosca segura com bloqueio, baixo desempenho por causa de
synchronized
.Versão 2:
Carregamento preguiçoso, segurança de rosca com alto desempenho e sem bloqueio.
fonte
Se você não precisar de carregamento lento, tente
Se você deseja um carregamento lento e deseja que seu Singleton seja seguro para threads, tente o padrão de verificação dupla
Como não é garantido que o padrão de verificação dupla funcione (devido a algum problema com os compiladores, não sei mais nada sobre isso.), Você também pode tentar sincronizar todo o método getInstance ou criar um registro para todos os seus Singletons.
fonte
volatile
Eu diria Enum Singleton
Singleton usando enum em Java geralmente é uma maneira de declarar enum singleton. Enum singleton pode conter variável de instância e método de instância. Por uma questão de simplicidade, observe também que, se você estiver usando qualquer método de instância, precisará garantir a segurança do encadeamento desse método, caso isso afete o estado do objeto.
O uso de um enum é muito fácil de implementar e não possui desvantagens em relação aos objetos serializáveis, que precisam ser contornados de outras maneiras.
Você pode acessá-lo por
Singleton.INSTANCE
, muito mais fácil do que chamar ogetInstance()
método no Singleton.Outro problema com os Singletons convencionais é que, quando você implementa a
Serializable
interface, eles não permanecem mais como Singleton porque oreadObject()
método sempre retorna uma nova instância como construtor em Java. Isso pode ser evitado usandoreadResolve()
e descartando a instância recém-criada substituindo por singleton como abaixoIsso pode se tornar ainda mais complexo se a sua Classe Singleton mantiver o estado, pois você precisará torná-los transitórios, mas com o Enum Singleton, a serialização é garantida pela JVM.
Boa leitura
fonte
fonte
Pode ser um pouco tarde para o jogo, mas há muitas nuances na implementação de um singleton. O padrão do suporte não pode ser usado em muitas situações. E IMO ao usar um volátil - você também deve usar uma variável local. Vamos começar do início e iterar no problema. Você verá o que eu quero dizer.
A primeira tentativa pode ser algo como isto:
Aqui temos a classe MySingleton, que possui um membro estático privado chamado INSTANCE, e um método estático público chamado getInstance (). Na primeira vez que getInstance () é chamado, o membro INSTANCE é nulo. O fluxo cairá na condição de criação e criará uma nova instância da classe MySingleton. As chamadas subseqüentes a getInstance () descobrirão que a variável INSTANCE já está definida e, portanto, não criará outra instância do MySingleton. Isso garante que haja apenas uma instância do MySingleton compartilhada entre todos os chamadores de getInstance ().
Mas esta implementação tem um problema. Aplicativos multithread terão uma condição de corrida na criação da instância única. Se vários encadeamentos de execução atingirem o método getInstance () aproximadamente (ou ao redor) ao mesmo tempo, cada um verá o membro INSTANCE como nulo. Isso resultará em cada thread criando uma nova instância do MySingleton e subsequentemente configurando o membro INSTANCE.
Aqui, usamos a palavra-chave sincronizada na assinatura do método para sincronizar o método getInstance (). Isso certamente corrigirá nossa condição de corrida. Threads agora bloquearão e entrarão no método, um de cada vez. Mas também cria um problema de desempenho. Essa implementação não apenas sincroniza a criação da instância única, mas também sincroniza todas as chamadas para getInstance (), incluindo leituras. As leituras não precisam ser sincronizadas, pois elas simplesmente retornam o valor de INSTANCE. Como as leituras compõem a maior parte de nossas chamadas (lembre-se, a instanciação ocorre apenas na primeira chamada), teremos um impacto desnecessário no desempenho sincronizando todo o método.
Aqui, movemos a sincronização da assinatura do método para um bloco sincronizado que envolve a criação da instância MySingleton. Mas isso resolve nosso problema? Bem, não estamos mais bloqueando as leituras, mas também demos um passo atrás. Vários encadeamentos atingirão o método getInstance () aproximadamente ou ao mesmo tempo e todos verão o membro INSTANCE como nulo. Em seguida, eles atingem o bloco sincronizado onde se obtém o bloqueio e cria a instância. Quando esse segmento sai do bloco, os outros segmentos disputam o bloqueio, e um por um cada segmento passa pelo bloco e cria uma nova instância da nossa classe. Então, estamos de volta onde começamos.
Aqui emitimos outro cheque do lado de dentro do bloco. Se o membro INSTANCE já tiver sido definido, pularemos a inicialização. Isso é chamado de bloqueio verificado duas vezes.
Isso resolve nosso problema de instanciação múltipla. Mas mais uma vez, nossa solução apresentou outro desafio. Outros threads podem não "ver" que o membro INSTANCE foi atualizado. Isso ocorre por que o Java otimiza as operações de memória. Os threads copiam os valores originais das variáveis da memória principal no cache da CPU. Alterações nos valores são gravadas e lidas nesse cache. Esse é um recurso do Java projetado para otimizar o desempenho. Mas isso cria um problema para nossa implementação de singleton. Um segundo segmento - sendo processado por uma CPU ou núcleo diferente, usando um cache diferente - não verá as alterações feitas pelo primeiro. Isso fará com que o segundo segmento veja o membro INSTANCE como nulo, forçando a criação de uma nova instância do nosso singleton.
Resolvemos isso usando a palavra-chave volátil na declaração do membro INSTANCE. Isso instruirá o compilador a sempre ler e gravar na memória principal e não no cache da CPU.
Mas essa mudança simples tem um custo. Como estamos ignorando o cache da CPU, sofreremos um impacto no desempenho toda vez que operarmos no membro instável volátil - o que fazemos quatro vezes. Verificamos duas vezes a existência (1 e 2), definimos o valor (3) e depois retornamos o valor (4). Alguém poderia argumentar que esse caminho é o caso complementar, pois apenas criamos a instância durante a primeira chamada do método. Talvez um impacto no desempenho na criação seja tolerável. Mas mesmo nosso principal caso de uso, lê, operará no membro volátil duas vezes. Uma vez para verificar a existência e novamente para retornar seu valor.
Como o impacto no desempenho deve-se a operar diretamente no membro volátil, vamos definir uma variável local para o valor do volátil e operar a variável local. Isso diminuirá o número de vezes que operamos no volátil, recuperando parte de nosso desempenho perdido. Observe que precisamos definir nossa variável local novamente quando inserirmos o bloco sincronizado. Isso garante que esteja atualizado com as alterações que ocorreram enquanto esperávamos o bloqueio.
Eu escrevi um artigo sobre isso recentemente. Desconstruindo o Singleton . Você pode encontrar mais informações sobre esses exemplos e um exemplo do padrão "suporte" lá. Há também um exemplo do mundo real que mostra a abordagem volátil verificada duas vezes. Espero que isto ajude.
fonte
BearerToken instance
no seu artigo não éstatic
? E o que é issoresult.hasExpired()
?class MySingleton
- talvez devesse serfinal
?BearerToken
instância não é estática porque faz parte doBearerTokenFactory
- que é configurado com um servidor de autorização específico. Pode haver muitosBearerTokenFactory
objetos - cada um com seu próprio "cache"BearerToken
que ele distribui até expirar. OhasExpired()
método noBeraerToken
é chamado noget()
método da fábrica para garantir que ele não entregue um token expirado. Se expirado, um novo token será solicitado para o servidor de autorização. O parágrafo após o bloco de código explica isso em mais detalhes.Isto é como implementar um simples
singleton
:É assim que você pode criar corretamente o seu
singleton
:fonte
getInstance()
. Mas, de fato, se você não possui outros métodos estáticos em sua classeSingleton
e apenas liga,getInstance()
não há diferença real.Você precisará verificar novamente o idioma se precisar carregar a variável de instância de uma classe lentamente. Se você precisar carregar uma variável estática ou um singleton preguiçosamente, precisará do idioma do titular da demanda por demanda .
Além disso, se o singleton precisar ser flexível, todos os outros campos precisarão ser transitórios e o método readResolve () precisará ser implementado para manter o objeto singleton invariável. Caso contrário, sempre que o objeto for desserializado, uma nova instância do objeto será criada. O que readResolve () faz é substituir o novo objeto lido por readObject (), que forçou a coleta de lixo desse novo objeto, pois não há variável referente a ele.
fonte
Várias maneiras de criar objeto singleton:
Conforme Joshua Bloch - Enum seria o melhor.
você também pode usar o bloqueio de verificação dupla.
Até a classe estática interna pode ser usada.
fonte
Enum singleton
A maneira mais simples de implementar um Singleton seguro para threads é usar um Enum
Este código funciona desde a introdução do Enum no Java 1.5
Bloqueio duplo verificado
Se você deseja codificar um singleton “clássico” que funcione em um ambiente multithread (a partir do Java 1.5), use este.
Isso não é seguro para threads antes da versão 1.5 porque a implementação da palavra-chave volátil foi diferente.
Carregamento Singleton antecipado (funciona mesmo antes do Java 1.5)
Essa implementação instancia o singleton quando a classe é carregada e fornece segurança de thread.
fonte
Para o JSE 5.0 e superior, use a abordagem Enum, caso contrário, use a abordagem estática de suporte de singleton (uma abordagem de carregamento lento descrita por Bill Pugh). A solução mais recente também é segura para threads, sem a necessidade de construções de linguagem especiais (por exemplo, voláteis ou sincronizadas).
fonte
Outro argumento frequentemente usado contra Singletons são seus problemas de testabilidade. Singletons não são facilmente ridicularizáveis para fins de teste. Se isso for um problema, eu gostaria de fazer a seguinte modificação:
O
setInstance
método adicionado permite definir uma implementação de maquete da classe singleton durante o teste:Isso também funciona com abordagens de inicialização antecipada:
Isso também tem a desvantagem de expor essa funcionalidade ao aplicativo normal. Outros desenvolvedores que trabalham nesse código podem ficar tentados a usar o método 'setInstance' para alterar uma função específica e, assim, alterar todo o comportamento do aplicativo; portanto, esse método deve conter pelo menos um bom aviso no seu javadoc.
Ainda assim, para a possibilidade de teste de maquete (quando necessário), essa exposição ao código pode ser um preço aceitável a ser pago.
fonte
classe singleton mais simples
fonte
Ainda acho que, após o java 1.5, o enum é a melhor implementação de singleton disponível, pois também garante que mesmo nos ambientes com vários threads - apenas uma instância seja criada.
public enum Singleton{ INSTANCE; }
e pronto !!!
fonte
Dê uma olhada neste post.
Exemplos de padrões de design do GoF nas principais bibliotecas do Java
Na seção "Singleton" da melhor resposta,
Você também pode aprender o exemplo de Singleton nas próprias classes nativas do Java.
fonte
O melhor padrão de singleton que eu já vi usa a interface do fornecedor.
Ver abaixo:
fonte
Às vezes, um simples "
static Foo foo = new Foo();
" não é suficiente. Pense em algumas inserções básicas de dados que você deseja fazer.Por outro lado, você teria que sincronizar qualquer método que instancia a variável singleton como tal. A sincronização não é ruim como tal, mas pode levar a problemas de desempenho ou bloqueio (em situações muito raras usando este exemplo. A solução é
Agora o que acontece? A classe é carregada através do carregador de classes. Diretamente depois que a classe foi interpretada a partir de uma matriz de bytes, a VM executa o estático {} - block. esse é o segredo: o bloco estático é chamado apenas uma vez, no momento em que a classe (nome) do pacote especificado é carregada por esse carregador de classe.
fonte
Como adicionamos a palavra-chave Synchronized antes de getInstance, evitamos a condição de corrida no caso em que dois threads chamam getInstance ao mesmo tempo.
fonte