Singleton - qual a melhor implementação?

O livro de Padrões de Projeto - Use a Cabeça - nos ensina a usar pelo menos duas implementações de singleton.

public class Singleton
{
	private static Singleton singleton;

	private Singleton(){}

	public synchronized Singleton getInstance()
	{
		if(singleton == null)
			singleton = new Singleton();

		return singleton;
	}
}

Essa primeira implementação utiliza um método sincronizado. O livro diz que sincronizar um método é caro (Isso é verdade, rsrs). Se várias threads estiverem sempre querendo adquirir uma referência ao objeto singleton, pode haver uma queda de desempenho, já que antes de entrar no método sincronizado, tem-se que obter o bloqueio.

A segunda implementação utiliza uma variável estática.

public class Singleton
{
	private static Singleton singleton = new Singleton();

	private Singleton(){}

	public Singleton getInstance()
	{
		return singleton;
	}
}

O livro não fala muita coisa a respeito desse segundo método. Ao meu ver, remove a obrigatoriedade de ter de obter o bloqueio do método. Quando a classe é carregada, um objeto singleton já é criado. Qualquer thread pode obter uma referência a esse objeto. Sempre irão obter uma referência ao mesmo objeto e entrar mais de uma thread no método getInstance não causa problema algum.

Assim, porque usaríamos o primeiro código em vez do segundo? Alguém poderia me fazer o favor de explicar mais pontos positivos e negativos do primeiro e segundo código?

Se é para usar uma variável estática e você está em um projeto com a versão do Java >= 1.5, considere o uso de Enums.
Enums, por excelência, são Singletons e, tal qual variáveis estáticas, são carregados na inicialização do projeto.
Aqui um exemplo.

Edit: Duplicou ao enviar.

Por mais básica que seja essa resposta, acredito que a questão depende da sua necessidade de sincronizar ou não.

Para certos cenários, por mais caro que seja, ou você sincroniza ou você terá tantos problemas que performance será o que menos vc irá se preocupar.

A primeira forma não funciona como deve e não deve ser usada.

O recomenado é iniciar a váriavel na declaração mesmo.

private static Singleton singleton = new Singleton();

A primeira forma ainda tá faltando a sincronização dentro do método, visto que um objeto poderá ser iniciado várias vezes em chamadas simultaneas. Mesmo asssim não deve ser usado.

Leia mais buscando por ‘double checked locking in singleton’.

Explicado por que não usar:
http://www.javaworld.com/jw-02-2001/jw-0209-double.html?page=1

Detalhes:
http://www.ibm.com/developerworks/java/library/j-dcl/index.html
http://en.wikipedia.org/wiki/Double-checked_locking

Obrigado pelos posts…vou ler os links de double locks…

Mas ainda não sei qual dos dois métodos é melhor e porque o primeiro não funciona…

Ao meu ver, o segundo é mais rápido que o primeiro. Não sei porque alguém usaria o primeiro, já que tem o atraso da sincronização.
Com relação ao que disseram sobre o método getInstance() não estar sincronizado no segundo, não vejo necessidade. Ele apenas retorna uma referência.

[quote=ECO2004]
O livro não fala muita coisa a respeito desse segundo método. Ao meu ver, remove a obrigatoriedade de ter de obter o bloqueio do método. Quando a classe é carregada, um objeto singleton já é criado. Qualquer thread pode obter uma referência a esse objeto. Sempre irão obter uma referência ao mesmo objeto e entrar mais de uma thread no método getInstance não causa problema algum.

Assim, porque usaríamos o primeiro código em vez do segundo? Alguém poderia me fazer o favor de explicar mais pontos positivos e negativos do primeiro e segundo código?[/quote]

O primeiro código é para que Lazy singleton. O lazy singleton é importante quanto o singleton é raramente usado e/ou é muito custoso em termos de recursos, e/ou não é garantido que existe. Desktop e Runtime são dois exemplos de singleton em java que raramente são usados e que quando são, custam. No caso do Desktop ele pode nem existir , e vai dar erro se tentar obter ele.

A melhor maneira de criar um singleton é não criar. A maior parte das vezes o que queremos é um Shared Object e não um singleton.
Singleton de verdade são raros em aplicações.

O lazy singleton é a regra para singletons de verdade porque normalmente é custoso criar o objeto ( por isso que ele é singleton para começo de conversa).
Para singletons de mentirinhas a instancia estática é melhor.

A implementação do padrão singleton que apresentou usa um Static Factory Method , mas o Effective Java demonstra que o singleton feito dessa forma é um objeto com muitos problemas técnicos (os singletons de verdade do java são controlados pela JVM, na realidade são “prespectivas” da JVM … ). Sobretudo se entrar serialização no barulho. A melhor forma atualmente para escreve um singleton é usar enum. Isso garante não apenas que ha uma instancia,como elimina um monte de problemas relativos a serilização e classloading.

Contudo, como disse, a melhor forma de implementar um singleton é não implementar.

O singleton é um padrão acadêmico que é usado em aula para estabelecer conceitos como static e “numero de instancias”, mas no mundo real ele não é muito prático e na realidade é muito complexo de implementar corretamente

A diferença está no fato que na primeira solução o singleton é lazy e na segunda não. Aí entra o desperdício, imagine que esse singleton é um objeto muito pesado, com a segunda solução ele seria carregado mesmo que você não o utilize, o que não acontece na primeira solução.

Falando sobre concorrência, é justamente essa a dificuldade de chegar a uma solução, conseguir fazer com que o singleton seja lazy. A sua primeira solução é assim, mas tem um grande problema: mesmo depois da inicialização, o acesso à instância ainda continua sendo synchronized, o que pode afetar muito a performance.

Eu ia omitir a resposta correta (não queria atrapalhar o seu aprendizado) mas ela já foi dada acima, double checked locking é a forma adequada de se fazer. A idéia é garantir que seja thread safe e ao mesmo tempo não mate a performance da aplicação.

Mas saiba que ainda há um problema relacionado à visibilidade por parte das threads, fica pra você estudar! :smiley:

Muito obrigado, pessoal!

Entendi o que disseram!

Abraço…

Wilson

[quote=von.juliano]A diferença está no fato que na primeira solução o singleton é lazy e na segunda não. Aí entra o desperdício, imagine que esse singleton é um objeto muito pesado, com a segunda solução ele seria carregado mesmo que você não o utilize, o que não acontece na primeira solução.

Falando sobre concorrência, é justamente essa a dificuldade de chegar a uma solução, conseguir fazer com que o singleton seja lazy. A sua primeira solução é assim, mas tem um grande problema: mesmo depois da inicialização, o acesso à instância ainda continua sendo synchronized, o que pode afetar muito a performance.

Eu ia omitir a resposta correta (não queria atrapalhar o seu aprendizado) mas ela já foi dada acima, double checked locking é a forma adequada de se fazer. [/quote]

double checked locking não é a forma correta de fazer. Isso é um anti-pattern. É só procurar no google.
Se vai usar loking a única forma correta é fazer o método inteiro ser sincronizado. É mais lento, sim. Mas é real. Não vai dar problemas. O double checking locking parece melhor, porque é mais rápido, mas tem muitos problemas não triviais que podem acontecer e ferrar com toda a lógica do sistema. Mais sobre isto aqui

O esquema é o seguinte:

  1. não use singleton. Não use mesmo. Muito provavelmente precisa de um Shared Object, não se um singleton. Singleton são raros!
  2. se vai usar singleton, então use enum para o implementar. Simples assim. Não tem o problema da sincronização nem muitos outros que a oslução classica que usa Static Factory Method tem.

Simples assim. Sem treta.
Para saber mais por favor leia o livro “Efective java” segunda edição. Aliás , quem não leu, leia. É bom para a cultura. :slight_smile:

@sergiotaborda A forma que ele mostrou está incompleta, mas o double checked locking seria a resposta correta se empregar o volatile. Ou estou enganado?

Certas coisas aparentemente precisam de um singleton, mas na verdade acabam precisando de um ThreadLocal (ou seja, uma instância do objeto por thread).

Isso ocorre quando a classe tem métodos que não são thread-safe. Um exemplo: classes SimpleDateFormat e DecimalFormat.

[quote=sergiotaborda][quote=ECO2004]
O livro não fala muita coisa a respeito desse segundo método. Ao meu ver, remove a obrigatoriedade de ter de obter o bloqueio do método. Quando a classe é carregada, um objeto singleton já é criado. Qualquer thread pode obter uma referência a esse objeto. Sempre irão obter uma referência ao mesmo objeto e entrar mais de uma thread no método getInstance não causa problema algum.

Assim, porque usaríamos o primeiro código em vez do segundo? Alguém poderia me fazer o favor de explicar mais pontos positivos e negativos do primeiro e segundo código?[/quote]

O primeiro código é para que Lazy singleton. O lazy singleton é importante quanto o singleton é raramente usado e/ou é muito custoso em termos de recursos, e/ou não é garantido que existe. Desktop e Runtime são dois exemplos de singleton em java que raramente são usados e que quando são, custam. No caso do Desktop ele pode nem existir , e vai dar erro se tentar obter ele.

A melhor maneira de criar um singleton é não criar. A maior parte das vezes o que queremos é um Shared Object e não um singleton.
Singleton de verdade são raros em aplicações.

O lazy singleton é a regra para singletons de verdade porque normalmente é custoso criar o objeto ( por isso que ele é singleton para começo de conversa).
Para singletons de mentirinhas a instancia estática é melhor.

A implementação do padrão singleton que apresentou usa um Static Factory Method , mas o Effective Java demonstra que o singleton feito dessa forma é um objeto com muitos problemas técnicos (os singletons de verdade do java são controlados pela JVM, na realidade são “prespectivas” da JVM … ). Sobretudo se entrar serialização no barulho. A melhor forma atualmente para escreve um singleton é usar enum. Isso garante não apenas que ha uma instancia,como elimina um monte de problemas relativos a serilização e classloading.

Contudo, como disse, a melhor forma de implementar um singleton é não implementar.

O singleton é um padrão acadêmico que é usado em aula para estabelecer conceitos como static e “numero de instancias”, mas no mundo real ele não é muito prático e na realidade é muito complexo de implementar corretamente
[/quote]

Concordo com Sergio. Singletons não são mais necessários em java pois a maioria dos casos pode ser resolvida com a Injeção de Dependência. Porém acho importante a aprendizagem dos padrões pois podem ser uteis em outras linguagens. Eu mesmo tenho alguns projetos em Delphi no qual uso Singletons devido a limitações da linguagem.

Singleton em java não combina com programação concorrente (threads) e possui alguns problemas de segurança devido a JVM que são muito bem esclarecidos no livro Java Efetivo, então realmente é melhor não utilizar!

Pessoal, por que em um singleton que usa “trava dupla”, a variável do tipo da classe tem que usar a palavra reservada volatile?
Que eu saiba, essa palavra é utiliza quando não se deseja persistir a informação.

public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;
  
     private DoubleCheckedLockingSingleton(){}
  
     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

SE usar o volatile sim, mas apenas se usar java 5 ou superior. volatile com java 4 não vai funcionar na mesma…
E se usa java 5 ou superior, enum é melhor que volatile , portanto, use enum e esqueça double checked locking.

No java 4 e antes, onde não pode usar enum, o método sincronizado é a solução correta.

[quote=ECO2004]Pessoal, por que em um singleton que usa “trava dupla”, a variável do tipo da classe tem que usar a palavra reservada volatile?
Que eu saiba, essa palavra é utiliza quando não se deseja persistir a informação.

[code]
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;

 private DoubleCheckedLockingSingleton(){}

 public DoubleCheckedLockingSingleton getInstance(){
     if(INSTANCE == null){
        synchronized(DoubleCheckedLockingSingleton.class){
            //double checking Singleton instance
            if(INSTANCE == null){
                INSTANCE = new DoubleCheckedLockingSingleton();
            }
        }
     }
     return INSTANCE;
 }

}
[/code][/quote]

Só lembrando que isso é necessário apenas em um ambiente multi thread! Para aplicações com somente uma thread não necessário todo esse trabalho.

[quote=ECO2004]Pessoal, por que em um singleton que usa “trava dupla”, a variável do tipo da classe tem que usar a palavra reservada volatile?
Que eu saiba, essa palavra é utiliza quando não se deseja persistir a informação.
[/quote]

Vc está confundindo com transient. Valatile significa que é seguro duas threads acessarem a variável concorrentemente. Contudo havia problemas com essa definição na especificação da jvm pré 5, o que levava cada vendedor a implementar diferente e não garantia o propósito que se pretende. Apenas no java 5 e depois, onde a especificação foi modificada para não ser ambigua, que a palavra tem alguma utilidade. Contudo no java 5 vieram as api do java.util.concurrent e os enuns, que eliminaram a necessidade de usar essa palavras explicitamente.

[quote=sergiotaborda][quote=ECO2004]Pessoal, por que em um singleton que usa “trava dupla”, a variável do tipo da classe tem que usar a palavra reservada volatile?
Que eu saiba, essa palavra é utiliza quando não se deseja persistir a informação.
[/quote]

Vc está confundindo com transient. Valatile significa que é seguro duas threads acessarem a variável concorrentemente. Contudo havia problemas com essa definição na especificação da jvm pré 5, o que levava cada vendedor a implementar diferente e não garantia o propósito que se pretende. Apenas no java 5 e depois, onde a especificação foi modificada para não ser ambigua, que a palavra tem alguma utilidade. Contudo no java 5 vieram as api do java.util.concurrent e os enuns, que eliminaram a necessidade de usar essa palavras explicitamente.[/quote]

Muito obrigado.

Realmente eu tava confundindo!

Se eu não usar volatile, o que pode acontecer?
Para mim não haveria mudança, pois a criação do objeto está protegida em um bloco sincronizado.

@sergiotaborda correto, eu também desencorajo o uso do singleton, mas como um problema para explicar concorrência, double checked locking tem seu valor.