O que é um Singleton?
Singleton é um Design Pattern que explora a necessidade
de se ter uma única instância de determinado objeto num sistema. Todos
os outros objetos precisam de apenas uma instância desse objeto. Imagine
a situação de um sistema de logs. Uma única instância deste componente
poderia ser criada para que todo o sistema acesse seus recursos de log. O
padrão Singleton garante que uma classe só seja iniciada uma vez.
Principais características
1. Um Singleton deve ter uma variável private static que referência a própria classe
2. Um Singleton deve ter seu constructor private ou protected
3. Deve ter um método de acesso que retorna uma única instância do objeto
O código abaixo constitui um exemplo de um componente
que cria uma Singleton de log fazendo com que o mesmo seja lazy
initialization (ou seja, inicialização preguiçosa, que só é instanciada
quando for necessária).
public class LogSingleton {
private static LogSingleton log;
private LogSingleton() {}
public LogSingleton getInstance() {
if ( log == null ) log = new LogSingleton();
return log;
}
public void append(String message) {
System.out.println( "[LOG] " + message + " [LOG]" );
}
}
Observe que neste exemplo, os três itens são atendidos.
Porém, este exemplo não é thread-safety. Isso significa que não é
garantido o bom funcionamento da Singleton num sistema onde se trabalhe
com multithreading. Imagine que existam duas threads A e B. A thread A
fez uma chamada à getInstance(). Ela veifica se log é nulo. Como foi a
primeira a acessar o objeto, o resultado é verdadeiro. Por conseqüência a
thread A passa para a segunda faze da condição – instanciar o objeto
LogSingleton. Porém, neste exato momento, a thread B fez uma chamada a
getInstance() e executa a regra condicional ( log == null ). Como a
thread A ainda não instanciou o objeto log, a thread B encontra um
resultado positivo na condição, fazendo com que ela também tente
instanciar log. Em seguida a thread A instancia log e depois a thread B
instancia mais uma vez log. O padrão Singleton deveria ser capaz de ser
instanciado uma única vez, portanto este exemplo não é thread-safety.
Utilizando a técnica double-check
Uma solução para o código anterior é torná-lo synchronized:
public LogSingleton getInstance() {
synchronized (LogSingleton.class) {
if ( log == null ) log = new LogSingleton();
}
return log;
}
Porém, a cada chamada de getInstance(), seria
necessário recorer ao recurso synchronized que é muito lento. Existe a
técnica de double-check que faz com que apenas a criação do objeto seja
sincronizada.
public LogSingleton getInstance() {
if ( log == null ) {
synchronized (LogSingleton.class) {
if ( log == null ) log = new LogSingleton();
}
}
return log;
}
Neste caso, imagine a situação: a thread A faz uma
chamada a getInstance() que, como log é nulo, entra no escopo
sincronizado. Neste ponto, nenhuma outra thread poderá instanciar o
objeto. A thread B chama getInstance() e, como log ainda não foi
inicializado, entra no início do escopo sincronizado, porém aguarda até
que a thread A termine seu serviço. Até aqui tudo bem. Neste ponto, nós
temos a segurança. O delay gerado pelo synchronized é necessário e
filtrado pelo primeiro IF. A thread A está no processo de instanciar o
log. O Virtual Machine ainda está levantando este objeto na memória.
Contudo, a thread C faz uma chamada a getInstance() que, encontra algo
diferente de null em log, porém log ainda não foi totalmente criado. A
thread C obteve a instância (inacabada) de log e em suas atividades fez
uma chamada ao método append() (mostrado no código completo). Como log
ainda não é verdadeiramente um LogSingleton, ele não tem capacidade de
chamar append(). Aqui encontramos uma situação perigosa e não
thread-safety. Portanto, a técnica double-ckeck não funciona, pois o log
== null não pode garantir sincronização na condição, proporcionando
situações como esta.
Singleton thread-safety sem a lazy initialization
Primeira opção é abandonar a lazy initialization:
public class LogSingleton {
private static LogSingleton log = new LogSingleton();
private LogSingleton() {}
public LogSingleton getInstance() {
return log;
}
public void append(String message) {
System.out.println( "[LOG] " + message + " [LOG]" );
}
}
Neste caso, nosso sistema teria que obrigatoriamente
instanciar log, mesmo que este nunca seja utilizado. Este caso é
thread-safety.
Singleton thread-safety com getInstance() sincronizado
public class LogSingleton {
private static LogSingleton log;
private LogSingleton() {}
public LogSingleton getInstance() {
synchronized (LogSingleton.class) {
if ( log == null ) log = new LogSingleton();
}
return log;
}
public void append(String message) {
System.out.println( "[LOG] " + message + " [LOG]" );
}
}
Como já foi mostrado, este caso é thread-safety, mas é
reconhecida uma perda de performace só pelo fato de entrarmos num escopo
sincronizado. A cada chamada a getInstance(), nosso sistema teria que
passar por esse recurso.
Singleton thread-safety com técnica initialize-on-demand holder class
Esta opção é a mais elegante. Observe o código:
public class LogSingleton {
private static class LogHolder {
private static final LogSingleton log = new LogSingleton();
}
private LogSingleton() {}
public LogSingleton getInstance() {
return LogHolder.log;
}
public void append(String message) {
System.out.println( "[LOG] " + message + " [LOG]" );
}
}
Esta técnica é interessante porque log não é
instanciado até que o método getInstance() seja chamado. Supondo que log
utilize muitos recursos, o sistema não seria obrigado a criar sua
instância até que o mesmo seja realmente necessário. A desvantagem deste
caso é que o objeto log é estático. Esta técnica não funciona com
objetos de instância. Quando alguma thread chamar getInstance(), uma
referência ao atributo log de LogHolder é realizada. Consequentemente a
classe interna é instanciada na memória. Este método é thread-safety e
só instancia LogSingleton pela real necessidade.
Qual técnica utilizar (conclusão)?
Certamente a double-check deve ser descartada, pois
apresenta falhas. Se você possui um Singleton que necessite ter uma
instância não estática (lazy initialization), você terá que sincronizar
getInstance() - opção thread-safety, porém muito lenta. Se uma
inicialização estática do objeto Singleton resolver o seu problema (como
em nosso exemplo de log), a initialize-on-demand é a melhor situação,
pois só criará o objeto quando o mesmo for necessário. Em alguns casos ,
você pode entender que o Singleton será sempre necessário – como um
caso de Singleton que faça as conexões com o banco de dados, por
exemplo. Neste caso, você poderia simplesmente utilizar a prática de
criá-lo como atributo estático na própria classe, o método mais simples.
Cada caso requer um estudo
para verificar a melhor opção a ser utilizada. Se estiver com dúvidas de
qual caso utilizar, proponho que use a initialize-on-demand.
"Faça ou não faça. A tentativa não existe." Yoda
Obrigado pela leitura.
Guilherme Pontes

Linux
Curso Java
Papers Java
Database
Hardware
Informática
Gestão de TI
Diversão
Download
Potions
Homepage
Blog
Facebook
Youtube
Twitter
Linubr.org
Nenhum comentário:
Postar um comentário