Menu
Coddy logo textTech

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");
challenge icon

Desafio

Fácil

Vamos 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 classe ConfigManager no namespace Configuration que 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 Instance que retorne a instância única (use a abordagem de inicialização estática mais simples para thread safety)
    • Incluir um campo de string privado _appName inicializado 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:

  1. Obtenha a instância de ConfigManager e imprima o nome padrão do app
  2. Obtenha a instância novamente (em uma variável diferente) e use-a para definir o novo nome do app a partir da entrada
  3. 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
ProductionServer

Isso 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
    }
}
quiz iconTeste seus conhecimentos

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