O que é uma maneira eficiente de implementar um padrão singleton em Java?
O que é uma maneira eficiente de implementar um padrão singleton em Java?
Respostas:
Use uma enumeração:
public enum Foo {
INSTANCE;
}
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 ):
A maneira correta de implementar um Singleton serializável
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Edit: Uma parte online do "Java Efetivo" diz:
"Essa abordagem é funcionalmente equivalente à abordagem de campo público, exceto que é mais concisa, fornece o mecanismo de serialização gratuitamente e oferece uma garantia rígida contra instanciações múltiplas, mesmo em face de sofisticados ataques de serialização ou reflexão. ainda a ser amplamente adotado, um tipo de enum de elemento único é a melhor maneira de implementar um singleton ".
Dependendo do uso, existem várias respostas "corretas".
Como o java5, a melhor maneira de fazer isso é usar um enum:
public enum Foo {
INSTANCE;
}
Antes do java5, o caso mais simples é:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
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 um private static final Foo
campo para armazenar a única instância e um public 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:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
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.
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
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.
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 é
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
Tudo é bom, exceto seu singleton carregado cedo. Vamos tentar o singleton carregado preguiçosamente
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
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
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
mas não é o suficiente para proteger o herói, sério !!! É o melhor que podemos / devemos fazer para ajudar nosso herói
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
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
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// Rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
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
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Sim, este é o nosso herói mesmo :)
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 é 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
public enum Foo {
INSTANCE;
}
Que internamente será tratado como
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
É isso aí! Não há mais medo de serialização, threads e código feio. Também ENUMS singleton são preguiçosamente inicializado .
Essa abordagem é funcionalmente equivalente à abordagem de campo público, exceto que é mais concisa, fornece o mecanismo de serialização gratuitamente e oferece uma garantia rígida contra instanciações múltiplas, mesmo em face de sofisticados ataques de serialização ou reflexão. Embora essa abordagem ainda não tenha sido amplamente adotada, um tipo de enum de elemento único é a melhor maneira de implementar um singleton.
-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 .
serialVersionUID
de 0L
. 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.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (E se getBar
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/iq2eayiR
Segmento de thread seguro em Java 5+:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
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.
Esqueça a inicialização lenta , é muito problemática. Esta é a solução mais simples:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
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.
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).
TicketNumberer
que precisa ter uma única instância global e onde você deseja escrever uma classe TicketIssuer
que contenha uma linha de código int ticketNumber = ticketNumberer.nextTicketNumber();
. No pensamento singleton tradicional, a linha de código anterior teria que ser algo parecido TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. No pensamento de DI, a classe teria um construtor public 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.
A seguir, são apresentadas três abordagens diferentes
1) Enum
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Travamento verificado / carregamento preguiçoso
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) Método estático de fábrica
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
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.
Versão 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Carregamento preguiçoso, rosca segura com bloqueio, baixo desempenho por causa de synchronized
.
Versão 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
Carregamento preguiçoso, segurança de rosca com alto desempenho e sem bloqueio.
Se você não precisar de carregamento lento, tente
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Se você deseja um carregamento lento e deseja que seu Singleton seja seguro para threads, tente o padrão de verificação dupla
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
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.
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.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Você pode acessá-lo por Singleton.INSTANCE
, muito mais fácil do que chamar o getInstance()
método no Singleton.
1.12 Serialização de constantes enum
As constantes enum são serializadas de maneira diferente dos objetos serializáveis ou externalizáveis comuns. A forma serializada de uma constante enum consiste apenas em seu nome; valores de campo da constante não estão presentes no formulário. Para serializar uma constante enum,
ObjectOutputStream
escreve o valor retornado pelo método de nome da constante enum. Para desserializar uma constante enum,ObjectInputStream
lê o nome da constante do fluxo; a constante desserializada é então obtida chamando ojava.lang.Enum.valueOf
método, passando o tipo de enum da constante junto com o nome da constante recebida como argumentos. Como outros objetos serializáveis ou externalizáveis, as constantes enum podem funcionar como alvos de referências anteriores que aparecem posteriormente no fluxo de serialização.O processo pelo qual constantes enum são serializados não pode ser personalizado: qualquer específico de classe
writeObject
,readObject
,readObjectNoData
,writeReplace
, ereadResolve
os métodos definidos por tipos enum são ignorados durante a serialização e desserialização. Da mesma forma, quaisquer declarações de camposerialPersistentFields
ouserialVersionUID
também são ignoradas - todos os tipos de enum têm um valor fixoserialVersionUID
de0L
. A documentação de campos e dados serializáveis para tipos de enumeração é desnecessária, pois não há variação no tipo de dados enviados.
Outro problema com os Singletons convencionais é que, quando você implementa a Serializable
interface, eles não permanecem mais como Singleton porque o readObject()
método sempre retorna uma nova instância como construtor em Java. Isso pode ser evitado usando readResolve()
e descartando a instância recém-criada substituindo por singleton como abaixo
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Isso 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
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
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:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
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.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return 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.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
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.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
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.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
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.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
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.
BearerToken instance
no seu artigo não é static
? E o que é isso result.hasExpired()
?
class MySingleton
- talvez devesse ser final
?
BearerToken
instância não é estática porque faz parte do BearerTokenFactory
- que é configurado com um servidor de autorização específico. Pode haver muitos BearerTokenFactory
objetos - cada um com seu próprio "cache" BearerToken
que ele distribui até expirar. O hasExpired()
método no BeraerToken
é chamado no get()
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
:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
É assim que você pode criar corretamente o seu singleton
:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()
. Mas, de fato, se você não possui outros métodos estáticos em sua classe Singleton
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.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
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.
Enum singleton
A maneira mais simples de implementar um Singleton seguro para threads é usar um Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
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.
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
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.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
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).
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:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
O setInstance
método adicionado permite definir uma implementação de maquete da classe singleton durante o teste:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Isso também funciona com abordagens de inicialização antecipada:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
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.
classe singleton mais simples
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
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 !!!
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,
Singleton (reconhecível pelos métodos criacionais que retornam a mesma instância (geralmente por si só) toda vez)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Você também pode aprender o exemplo de Singleton nas próprias classes nativas do Java.
O melhor padrão de singleton que eu já vi usa a interface do fornecedor.
Ver abaixo:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
À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 é
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
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.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
if (INSTANCE != null)
throw new IllegalStateException (“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
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.