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 impedir 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 — se várias 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.

C# oferece uma abordagem mais simples e segura para threads 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 é segura para threads porque o CLR garante que a inicialização de campos estáticos acontece 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 criar um gerenciador de configuração seguro para threads usando o padrão Singleton. Em aplicações reais, as configurações precisam estar acessíveis em todo o programa, garantindo que exista apenas uma instância — um caso de uso perfeito para 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 armazenar a única instância
    • Usar um construtor privado para impedir a instanciação externa
    • Fornecer uma propriedade estática Instance que retorne a única instância (use a abordagem mais simples de inicialização estática para garantir a segurança em relação a threads)
    • Incluir um campo privado do tipo string _appName inicializado com "MyApp"
    • Ter um método SetAppName(string name) para atualizar o nome do aplicativo
    • Ter um método GetAppName() que retorne o nome atual do aplicativo
  • Program.cs: no 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 aplicativo a ser definido (por exemplo, ProductionServer)

No seu programa:

  1. Obtenha a instância de ConfigManager e imprima o nome padrão do aplicativo
  2. Obtenha a instância novamente (em uma variável diferente) e use-a para definir o novo nome do aplicativo recebido na entrada
  3. Obtenha a instância uma terceira vez e imprima o nome do aplicativo para provar que todas as referências compartilham os mesmos dados

Por exemplo, se a entrada for ProductionServer, a saída deverá ser:

MyApp
ProductionServer

Isso demonstra o principal benefício do Singleton: não importa quantas vezes você acesse Instance, sempre obterá o mesmo objeto com estado compartilhado. A abordagem de inicialização estática garante a segurança em relação a threads porque o CLR garante que o campo estático seja inicializado apenas uma vez, mesmo que várias threads o acessem simultaneamente.

Experimente você mesmo

using System;
using Configuration;

class Program
{
    public static void Main(string[] args)
    {
        // Lê 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

Pratique por conta própria: Compilador de C# online