O padrão Singleton garante que uma classe tenha apenas uma instância e fornece um ponto global de acesso a essa instância. Este padrão é particularmente útil quando precisamos controlar o acesso a recursos compartilhados como conexões de banco de dados ou arquivos de configuração.
Implementação Básica
using System;
UnicoServico.ObterInstancia().ExibirIdentificador();
UnicoServico.ObterInstancia().ExibirIdentificador();
UnicoServico.ObterInstancia().ExibirIdentificador();
class UnicoServico
{
private Guid _identificador;
private static UnicoServico _instancia;
private UnicoServico()
{
_identificador = Guid.NewGuid();
}
public static UnicoServico ObterInstancia()
{
if (_instancia == null)
{
_instancia = new UnicoServico();
}
return _instancia;
}
public void ExibirIdentificador() => Console.WriteLine(_identificador);
}
Problemas em Ambientes Concorrentes
A implementação básica não é segura para ambientes com múltiplos threads. Em cenários concorrentes, múltiplas instâncias podem ser criadas:
using System.Threading.Tasks;
// Em ambientes concorrentes, não garantimos que sempre será a mesma instância
Parallel.For(0, 3, indice =>
{
var id = UnicoServico.ObterInstancia().ObterIdentificador();
Console.WriteLine($"Execução {indice}: {id}");
});
class UnicoServico
{
private Guid _identificador;
private static UnicoServico _instancia;
private UnicoServico()
{
_identificador = Guid.NewGuid();
}
public static UnicoServico ObterInstancia()
{
if (_instancia == null)
{
_instancia = new UnicoServico();
}
return _instancia;
}
public Guid ObterIdentificador() => _identificador;
}
Saída típica em ambiente concorretne:
Execução 1: 1624c990-26e5-4c73-add1-fc3b736c1059
Execução 0: 7782f74d-9100-4115-9d37-c5a2cd40f481
Execução 2: 2ab01b9a-48c3-45af-b613-ced3def060b5
Implementação com Controle de Concorrência
Para resolver problemas de concorrência, podemos implementar um mecanismo de bloqueio:
using System.Threading.Tasks;
// Com mecanismo de bloqueio, garantimos a mesma instância mesmo em ambientes concorrentes
Parallel.For(0, 30, indice =>
{
var id = UnicoServico.ObterInstancia().ObterIdentificador();
Console.WriteLine($"Execução {indice}: {id}");
});
class UnicoServico
{
private Guid _identificador;
private static readonly object _bloqueio = new object();
private static UnicoServico _instancia;
private UnicoServico()
{
_identificador = Guid.NewGuid();
}
public static UnicoServico ObterInstancia()
{
if (_instancia == null)
{
lock (_bloqueio)
{
if (_instancia == null)
{
_instancia = new UnicoServico();
}
}
}
return _instancia;
}
public Guid ObterIdentificador() => _identificador;
}
Implementação com Double-Check Locking
Para melhorar o desempenho, podemos usar o padrão Double-Check Locking:
class UnicoServico
{
private Guid _identificador;
private static readonly object _bloqueio = new object();
private static UnicoServico _instancia;
private UnicoServico()
{
_identificador = Guid.NewGuid();
}
public static UnicoServico ObterInstancia()
{
if (_instancia == null)
{
lock (_bloqueio)
{
if (_instancia == null)
{
_instancia = new UnicoServico();
}
}
}
return _instancia;
}
public Guid ObterIdentificador() => _identificador;
}
Altenrativa Moderna com Lazy Initialization
No .NET 4.0 e posteriores, podemos usar a classe Lazy<T> para uma implementação mais simples e eficeinte:
class UnicoServico
{
private Guid _identificador;
private static readonly Lazy<UnicoServico> _instanciaLazy =
new Lazy<UnicoServico>(() => new UnicoServico());
private UnicoServico()
{
_identificador = Guid.NewGuid();
}
public static UnicoServico ObterInstancia() => _instanciaLazy.Value;
public Guid ObterIdentificador() => _identificador;
}
Esta abordagem é thread-safe por padrão e oferece inicialização preguiçosa (lazy initialization) com alto desempenho.