Singleton é um dos padrões de design de criação mais amplamente utilizados para restringir o objeto criado por aplicações. Se estiver sendo utilizado em um ambiente multi-threaded, a segurança de thread da classe singleton é muito importante. Em aplicações do mundo real, recursos como conexões de banco de dados ou Sistemas de Informação Empresarial (EIS) são limitados e devem ser utilizados com sabedoria para evitar qualquer escassez de recursos. Para alcançar isso, podemos implementar um padrão de design Singleton. Podemos criar uma classe de envoltório para o recurso e limitar o número de objetos criados em tempo de execução para um.
Singleton Thread Safe em Java
Em geral, seguimos as etapas abaixo para criar uma classe singleton:
- Crie um construtor privado para evitar a criação de novos objetos com o operador new.
- Declare uma instância estática privada da mesma classe.
- Fornecer um método público estático que retornará a variável de instância da classe singleton. Se a variável não estiver inicializada, inicialize-a; caso contrário, retorne simplesmente a variável de instância.
Usando as etapas acima, criei uma classe singleton que parece abaixo. ASingleton.java
package com.journaldev.designpatterns;
public class ASingleton {
private static ASingleton instance = null;
private ASingleton() {
}
public static ASingleton getInstance() {
if (instance == null) {
instance = new ASingleton();
}
return instance;
}
}
No código acima, o método getInstance() não é seguro para threads. Múltiplas threads podem acessá-lo ao mesmo tempo. Para as primeiras threads quando a variável de instância não está inicializada, múltiplas threads podem entrar no loop if e criar múltiplas instâncias. Isso quebrará nossa implementação singleton.
Como alcançar segurança de thread em uma Classe Singleton?
Existem três maneiras pelas quais podemos alcançar segurança de thread.
- Crie a variável de instância no momento do carregamento da classe.
Prós :
- Segurança de thread sem sincronização
- Fácil de implementar
Contras :
- Criação precoce de recurso que pode não ser usado na aplicação.
- A aplicação cliente não pode passar nenhum argumento, então não podemos reutilizá-lo. Por exemplo, tendo uma classe singleton genérica para conexão de banco de dados onde a aplicação cliente fornece as propriedades do servidor de banco de dados.
- Sincronize o método getInstance().
Prós :
- A segurança de segmento é garantida.
- A aplicação do cliente pode passar parâmetros
- Inicialização preguiçosa alcançada
Contras:
- Desempenho lento devido ao overhead de bloqueio.
- Sincronização desnecessária que não é necessária uma vez que a variável de instância é inicializada.
- Use bloco sincronizado dentro do loop if e variável volátil
Prós:
- A segurança de segmento é garantida
- A aplicação do cliente pode passar argumentos
- Inicialização preguiçosa alcançada
- O overhead de sincronização é mínimo e aplicável apenas para os primeiros threads quando a variável é nula.
Contras:
- Condição extra se
Olhando para todas as três maneiras de alcançar a segurança de segmento, acredito que a terceira é a melhor opção. Nesse caso, a classe modificada ficará assim:
package com.journaldev.designpatterns;
public class ASingleton {
private static volatile ASingleton instance;
private static Object mutex = new Object();
private ASingleton() {
}
public static ASingleton getInstance() {
ASingleton result = instance;
if (result == null) {
synchronized (mutex) {
result = instance;
if (result == null)
instance = result = new ASingleton();
}
}
return result;
}
}
A variável local result
parece desnecessária. Mas está lá para melhorar o desempenho do nosso código. Nos casos em que a instância já está inicializada (na maioria das vezes), o campo volátil é acessado apenas uma vez (devido a “return result;” em vez de “return instance;”). Isso pode melhorar o desempenho do método em até 25 por cento. Se você achar que existem maneiras melhores de alcançar isso ou se a segurança de segmento estiver comprometida na implementação acima, por favor, comente e compartilhe conosco.
Dica Extra
String não é uma escolha muito boa para ser usada com a palavra-chave synchronized. Isso ocorre porque elas são armazenadas em um pool de strings e não queremos travar uma string que pode estar sendo usada por outro trecho de código. Então estou usando uma variável de tipo Object. Saiba mais sobre sincronização e segurança de thread em Java.
Você pode conferir mais exemplos de Java em nosso Repositório no GitHub.
Source:
https://www.digitalocean.com/community/tutorials/thread-safety-in-java-singleton-classes