O padrão de projetos Singleton consiste em uma forma de garantirmos que teremos uma única instância de uma determinada classe no programa atual.
Por exemplo, em um programa Java para desktop podemos criar um Singleton da classe que gerencia a conexão com o banco de dados.
Este é um dos design patterns mais simples que existem, mas ele possui algumas nuances importantes de se entender do ponto de vista de implementação.
Implementação inicial em Java
Para garantirmos uma única instância de uma classe, a abordagem mais comum é criar um método estático que retorne sempre o mesmo objeto. Exemplo:
public class Singlegon {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() { }
}
O atributo estático instance
armazena o objeto criado para retornar a cada chamada de getInstance()
.
O trecho private Singleton() { };
é um construtor privado, garantindo que nenhuma outra classe poderá criar inadvertidamente uma instância desta.
Postergando a criação do objeto
Tudo ok, mas nem sempre queremos criar o objeto no modo agressivo (eager), isto é, instanciá-lo logo que a classe é carregada. Em muitas situações, é desejável postergar a criação do objeto até a primeira chamada. Exemplo:
public class Singlegon {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
No código acima, o primeiro acesso ao método getInstance()
irá disparar a criação do objeto, que será então retornada nas demais chamadas.
Problemas de sincronizção
O problema do código acima é que se houver mais de uma chamada concorrente no primeiro acesso ao método getInstance()
ele pode criar duas instâncias de Teste
. Duas threads poderiam entrar dentro do if
, certo?
A solução mais básica para isso é sincronizar o método:
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
O problema desta abordagem é que todas as chamadas estarão sujeitas a bloqueios, deixando a execução geral do programa mais lenta. Imagine um método assim num servidor de aplicação com vários usuários acessando o sistema! É terrível.
Uma solução melhor seria um bloco synchronized
dentro do if
, assim:
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
Isso resolve o problema da sincronização em todos os acessos, mas é uma solução “ingênua”, pois na verdade voltamos ao problema inicial. Como o if
não está sincronizado, duas threads diferentes podem entrar no bloco de criação ao mesmo tempo e, mesmo com a sincronização, elas retornarão instâncias diferentes quando instance == null
.
Então, a solução mais “pura” para o singleton pattern seria acrescentar uma verificação dupla, assim:
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
Com esta última abordagem garantimos que não haverá perda de desempenho por causa de sincronização desnecessária do método inteiro.
Além disso, garantimos uma única instância de Teste
, pois mesmo que duas chamadas concorrentes entrem dentro do primeiro if
, temos uma nova verificação sincronizada.
No pior caso, se houver duas ou mais chamadas concorrentes no primeiro acesso a getInstance
(quando INSTANCE
ainda é null
), apenas estas primeiras chamadas serão sincronizadas, sendo que após a primeira atribuição de INSTANCE
, nenhuma chamada posterior será sincronizada.
Além do Singleton: padrão Registry
Alguns argumentam que o padrão Singleton está depreciado e deve ser abandonado. De certa forma eu concordo, pois em ambientes onde múltiplas threads e múltiplas aplicações executam concorrentemente, ter apenas um objeto quase nunca é desejável.
O padrão de projeto Registry permite armazenarmos uma coleção de objetos, cada um contendo um identificador ou um escopo específicos. É como se limitássemos o escopo do Singleton de “um objeto por programa” para “um objeto por qualquer escopo que quisermos“.
A implementação varia muito, mas podemos encontrar exemplos claros desse padrão em:
- Threadlocal, que permite armazenar valores para uma thread, ou seja, um Singleton para cada uma.
- HttpSession, que retorna sempre o mesmo objeto para cada usuário do sistema web, ou seja, um Singleton por usuário.
- Frameworks de Injeção de Dependências como Spring ou CDI, os quais gerenciam a criação de objetos em diferentes escopos e permitem inclusive usar o padrão Singleton declarativamente.
Não entrarei em detalhes sobre o Registry neste artigo.
Indicações de Leitura
Uma leitura mais completa sobre o assunto está no Head First Design Patterns (Use a Cabeça! Padrões de Projeto). Embora tendo suas falhas, este é um livro muito bom para quem ainda está começando a entender Padrões de Projetos.
Outros detalhes interessantes sobre Singleton, como variações de implementação, podem ser encontrados na Wikipédia (em Inglês).
Uma breve definição do padrão Registry pode se encontra no catálogo de padrões do Martin Fowler.