Singleton Thread-Safe
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 52 de 70.
O padrão Singleton garante que uma classe tenha apenas uma instância em toda a sua aplicação e fornece um ponto de acesso global a ela. Isso é útil para gerenciar recursos compartilhados, como configurações, serviços de log ou conexões de banco de dados.
Um Singleton básico usa um construtor privado para evitar a instanciação externa e uma propriedade estática para fornecer acesso:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Construtor privado
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}A instrução lock garante a segurança de threads (thread safety) - se múltiplas threads tentarem acessar Instance simultaneamente, apenas uma poderá entrar na seção crítica por vez. Sem isso, duas threads poderiam ver _instance como nulo e criar instâncias separadas.
O C# oferece uma abordagem thread-safe mais simples usando inicialização estática:
public class Logger
{
private static readonly Logger _instance = new Logger();
private Logger() { }
public static Logger Instance => _instance;
public void Log(string message) => Console.WriteLine(message);
}Esta versão é thread-safe porque o CLR garante que a inicialização de campos estáticos ocorra apenas uma vez. Ambas as abordagens garantem que você sempre obtenha a mesma instância:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Desafio
FácilVamos construir um gerenciador de configuração thread-safe usando o padrão Singleton. Em aplicações reais, as configurações precisam estar acessíveis em todo o programa, garantindo que apenas uma instância exista - um caso de uso perfeito para o Singleton.
Você organizará seu código em dois arquivos:
ConfigManager.cs: Crie uma classeConfigManagerno namespaceConfigurationque implemente o padrão Singleton. Seu gerenciador de configuração deve:- Ter um campo estático privado para manter a instância única
- Usar um construtor privado para evitar a instanciação externa
- Fornecer uma propriedade estática
Instanceque retorne a instância única (use a abordagem de inicialização estática mais simples para thread safety) - Incluir um campo de string privado
_appNameinicializado como"MyApp" - Ter um método
SetAppName(string name)para atualizar o nome do app - Ter um método
GetAppName()que retorne o nome atual do app
Program.cs: Em seu arquivo principal, demonstre que o Singleton funciona corretamente acessando a instância várias vezes e mostrando que as alterações persistem em todas as referências.
Você receberá uma entrada:
- Um novo nome de aplicação para definir (ex:
ProductionServer)
Em seu programa:
- Obtenha a instância de
ConfigManagere imprima o nome padrão do app - Obtenha a instância novamente (em uma variável diferente) e use-a para definir o novo nome do app a partir da entrada
- Obtenha a instância uma terceira vez e imprima o nome do app para provar que todas as referências compartilham os mesmos dados
Por exemplo, se a entrada for ProductionServer, a saída deve ser:
MyApp
ProductionServerIsso demonstra o principal benefício do Singleton - não importa quantas vezes você acesse Instance, você sempre obtém o mesmo objeto com estado compartilhado. A abordagem de inicialização estática garante a thread safety porque o CLR assegura que o campo estático seja inicializado apenas uma vez, mesmo que múltiplas threads o acessem simultaneamente.
Folha de consulta
O padrão Singleton garante que uma classe tenha apenas uma instância em toda a sua aplicação e fornece um ponto de acesso global a ela.
Singleton básico com bloqueio explícito para segurança de threads (thread safety):
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Construtor privado
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}A instrução lock garante a segurança de threads ao permitir que apenas uma thread entre na seção crítica por vez.
Singleton thread-safe mais simples usando inicialização estática:
public class Logger
{
private static readonly Logger _instance = new Logger();
private Logger() { }
public static Logger Instance => _instance;
public void Log(string message) => Console.WriteLine(message);
}Esta abordagem é thread-safe porque o CLR garante que a inicialização de campos estáticos ocorra apenas uma vez.
Uso - sempre retorna a mesma instância:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Experimente você mesmo
using System;
using Configuration;
class Program
{
public static void Main(string[] args)
{
// Ler a entrada - o novo nome da aplicação
string newAppName = Console.ReadLine();
// TODO: Obter a instância de ConfigManager e imprimir o nome padrão da aplicação
// TODO: Obter a instância novamente (em uma variável diferente) e usá-la para definir o novo nome da aplicação
// TODO: Obter a instância uma terceira vez e imprimir o nome da aplicação para provar que todas as referências compartilham os mesmos dados
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosNamespaces e DiretivasIntrodução a Classes e ObjetosA Palavra-chave 'this'Métodos e ParâmetrosCampos vs PropriedadesConstrutoresInicializadores de ObjetosRecapitulação - Calculadora Simples4Herança
Sintaxe Básica de Herança (:)A Palavra-chave 'base'Palavras-chave Virtual e OverrideClasses SeladasA Classe Base 'object'Recapitulação - Hierarquia de Funcionários7Recursos Avançados
Sobrecarga de OperadoresIndexadores (this[])Sobrescrita de ToString()Métodos de ExtensãoRecapitulação - Lista Personalizada10Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoSingleton Thread-SafePadrão FactoryPadrão Observer (Eventos)Padrão Strategy2Propriedades e Membros Estáticos
Propriedades AutoimplementadasPropriedades de Apenas Leitura/EscritaCampos e Métodos EstáticosClasses EstáticasMembros com Corpo de Expressão5Polimorfismo e Interfaces
Polimorfismo: Compilação vs ExecuçãoInterface vs Classe AbstrataMúltiplas InterfacesInterfaces ExplícitasUpcasting e DowncastingRecapitulação - Calculadora de Formas8Conceitos Avançados de POO
Composição sobre HerançaGenerics (Classes e Métodos)Delegates e EventosAtributos e ReflectionIDisposable e a Instrução usingFundamentos de Injeção de Dependência11Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão Composite3Arquitetura de Classes
Dados de Instância vs EstáticosPalavras-chave 'readonly' e 'const'Campos de SuporteRecapitulação - Gerenciador de Conta Bancária6Encapsulamento
Modificadores de AcessoPropriedades para EncapsulamentoImplementação de Ocultação de DadosPadrões de ImutabilidadeRecapitulação - Registros de Estudantes9Argumentos Variáveis
A Palavra-chave 'params'Parâmetros OpcionaisArgumentos NomeadosSobrecarga de Métodos