O que é um singleton?
Um singleton é um dos padrões de design mais simples que pode ser aplicado a uma classe. Às vezes, as pessoas dizem “esta classe é um singleton”, o que significa que esta classe implementa o padrão de design singleton. Às vezes é necessário escrever uma classe para a qual apenas um objeto possa ser criado. Por exemplo, uma classe responsável por registrar ou conectar-se a um banco de dados. O padrão de design Singleton descreve como podemos realizar tal tarefa. Um singleton é um padrão de design que faz duas coisas:-
Fornece uma garantia de que uma classe terá apenas uma instância da classe.
-
Fornece um ponto de acesso global para uma instância desta classe.
-
Construtor privado. Restringe a capacidade de criar objetos de classe fora da própria classe.
-
Um método estático público que retorna uma instância da classe. Este método é chamado
getInstance
. Este é o ponto de acesso global à instância da classe.
Opções de implementação
O padrão de design singleton é usado de diferentes maneiras. Cada opção é boa e ruim à sua maneira. Aqui, como sempre: não existe ideal, mas é preciso lutar por isso. Mas antes de mais nada, vamos definir o que é bom e o que é ruim, e quais métricas influenciam a avaliação da implementação de um padrão de design. Vamos começar com o positivo. Aqui estão os critérios que conferem suculência e atratividade à implementação:-
Inicialização lenta: quando uma classe é carregada enquanto o aplicativo está sendo executado exatamente quando é necessário.
-
Simplicidade e transparência do código: a métrica, claro, é subjetiva, mas importante.
-
Segurança de thread: funciona corretamente em um ambiente multithread.
-
Alto desempenho em um ambiente multithread: os threads bloqueiam uns aos outros minimamente ou não bloqueiam nenhum ao compartilhar um recurso.
-
Inicialização não lenta: quando uma classe é carregada quando a aplicação é iniciada, independentemente de ser necessária ou não (um paradoxo, no mundo da TI é melhor ser preguiçoso)
-
Complexidade e baixa legibilidade do código. A métrica também é subjetiva. Assumiremos que se o sangue sair dos olhos, a implementação é razoável.
-
Falta de segurança do fio. Em outras palavras, “perigo do fio”. Operação incorreta em um ambiente multithread.
-
Baixo desempenho em um ambiente multithread: os threads bloqueiam uns aos outros o tempo todo ou frequentemente ao compartilhar um recurso.
Código
Agora estamos prontos para considerar várias opções de implementação, listando os prós e os contras:Solução Simples
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
A implementação mais simples. Prós:
-
Simplicidade e transparência do código
-
Segurança do fio
-
Alto desempenho em um ambiente multithread
- Inicialização não preguiçosa.
Inicialização lenta
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Prós:
-
Inicialização lenta.
-
Não é seguro para threads
Acessador sincronizado
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Prós:
-
Inicialização lenta.
-
Segurança do fio
-
Baixo desempenho em um ambiente multithread
getInstance
está sincronizado e você só pode inseri-lo um de cada vez. Na verdade, não precisamos sincronizar todo o método, mas apenas aquela parte dele na qual inicializamos um novo objeto de classe. Mas não podemos simplesmente agrupar synchronized
a parte responsável pela criação de um novo objeto em um bloco: isso não fornecerá segurança ao thread. É um pouco mais complicado. O método de sincronização correto é fornecido abaixo:
Bloqueio verificado duas vezes
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Prós:
-
Inicialização lenta.
-
Segurança do fio
-
Alto desempenho em um ambiente multithread
-
Não suportado em versões Java inferiores a 1.5 (a palavra-chave volátil foi corrigida na versão 1.5)
INSTANCE
deve ser final
, ou volatile
. A última implementação que discutiremos hoje é Class Holder Singleton
.
Titular da classe Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Prós:
-
Inicialização lenta.
-
Segurança do fio.
-
Alto desempenho em um ambiente multithread.
-
Para o correto funcionamento é necessário garantir que o objeto da classe
Singleton
seja inicializado sem erros. Caso contrário, a primeira chamada de métodogetInstance
terminará em erroExceptionInInitializerError
e todas as subsequentes falharãoNoClassDefFoundError
.
Implementação | Inicialização lenta | Segurança do fio | Velocidade multithread | Quando usar? |
---|---|---|---|---|
Solução Simples | - | + | Rápido | Nunca. Ou quando a inicialização lenta não é importante. Mas nunca melhor. |
Inicialização lenta | + | - | Não aplicável | Sempre quando o multithreading não é necessário |
Acessador sincronizado | + | + | Devagar | Nunca. Ou quando a velocidade de trabalho com multithreading não importa. Mas nunca melhor |
Bloqueio verificado duas vezes | + | + | Rápido | Em casos raros, quando você precisa lidar com exceções ao criar um singleton. (quando Class Holder Singleton não é aplicável) |
Titular da classe Singleton | + | + | Rápido | Sempre quando o multithreading é necessário e há uma garantia de que um objeto de classe singleton será criado sem problemas. |
Prós e contras do padrão Singleton
Em geral, o singleton faz exatamente o que se espera dele:-
Fornece uma garantia de que uma classe terá apenas uma instância da classe.
-
Fornece um ponto de acesso global para uma instância desta classe.
-
Singleton viola o SRP (Princípio de Responsabilidade Única) – a classe Singleton, além de suas responsabilidades imediatas, também controla o número de suas cópias.
-
A dependência de uma classe ou método regular em um singleton não é visível no contrato público da classe.
-
Variáveis globais são ruins. O singleton eventualmente se transforma em uma variável global robusta.
-
A presença de um singleton reduz a testabilidade da aplicação em geral e das classes que utilizam o singleton em particular.
GO TO FULL VERSION