Menu
Coddy logo textTech

Singleton Thread-Safe

Fait partie de la section Programmation Orientée Objet du Journey C# de Coddy — leçon 52 sur 70.

Le pattern Singleton garantit qu'une classe n'a qu'une seule instance dans toute votre application et fournit un point d'accès global à celle-ci. Ceci est utile pour gérer des ressources partagées telles que les paramètres de configuration, les services de journalisation ou les connexions aux bases de données.

Un Singleton de base utilise un constructeur privé pour empêcher l'instanciation externe et une propriété statique pour fournir l'accès :

public class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();
    
    private Logger() { }  // Constructeur privé
    
    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new Logger();
                return _instance;
            }
        }
    }
    
    public void Log(string message) => Console.WriteLine(message);
}

L'instruction lock garantit la sécurité des threads (thread safety) - si plusieurs threads tentent d'accéder à Instance simultanément, un seul peut entrer dans la section critique à la fois. Sans cela, deux threads pourraient tous deux voir _instance comme étant nul et créer des instances distinctes.

C# propose une approche thread-safe plus simple utilisant l'initialisation statique :

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);
}

Cette version est thread-safe car le CLR garantit que l'initialisation des champs statiques ne se produit qu'une seule fois. Les deux approches garantissent que vous obtenez toujours la même instance :

Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");
challenge icon

Défi

Facile

Construisons un gestionnaire de configuration thread-safe en utilisant le pattern Singleton. Dans les applications réelles, les paramètres de configuration doivent être accessibles dans tout le programme tout en garantissant qu'une seule instance existe - un cas d'utilisation parfait pour le Singleton.

Vous organiserez votre code sur deux fichiers :

  • ConfigManager.cs : Créez une classe ConfigManager dans l'espace de noms Configuration qui implémente le pattern Singleton. Votre gestionnaire de configuration doit :
    • Avoir un champ statique privé pour contenir l'instance unique
    • Utiliser un constructeur privé pour empêcher l'instanciation externe
    • Fournir une propriété statique Instance qui retourne l'instance unique (utilisez l'approche d'initialisation statique plus simple pour la sécurité des threads)
    • Inclure un champ de chaîne privé _appName initialisé à "MyApp"
    • Avoir une méthode SetAppName(string name) pour mettre à jour le nom de l'application
    • Avoir une méthode GetAppName() qui retourne le nom actuel de l'application
  • Program.cs : Dans votre fichier principal, démontrez que le Singleton fonctionne correctement en accédant à l'instance plusieurs fois et en montrant que les modifications persistent à travers toutes les références.

Vous recevrez une entrée :

  • Un nouveau nom d'application à définir (par exemple, ProductionServer)

Dans votre programme :

  1. Récupérez l'instance de ConfigManager et affichez le nom de l'application par défaut
  2. Récupérez à nouveau l'instance (dans une variable différente) et utilisez-la pour définir le nouveau nom de l'application à partir de l'entrée
  3. Récupérez l'instance une troisième fois et affichez le nom de l'application pour prouver que toutes les références partagent les mêmes données

Par exemple, si l'entrée est ProductionServer, la sortie devrait être :

MyApp
ProductionServer

Cela démontre l'avantage principal du Singleton - peu importe le nombre de fois que vous accédez à Instance, vous obtenez toujours le même objet avec un état partagé. L'approche d'initialisation statique garantit la sécurité des threads car le CLR s'assure que le champ statique n'est initialisé qu'une seule fois, même si plusieurs threads y accèdent simultanément.

Aide-mémoire

Le pattern Singleton garantit qu'une classe n'a qu'une seule instance dans toute votre application et fournit un point d'accès global à celle-ci.

Singleton de base avec verrouillage explicite pour la sécurité des threads (thread safety) :

public class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();
    
    private Logger() { }  // Constructeur privé
    
    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new Logger();
                return _instance;
            }
        }
    }
    
    public void Log(string message) => Console.WriteLine(message);
}

L'instruction lock garantit la sécurité des threads en ne permettant qu'à un seul thread d'entrer dans la section critique à la fois.

Singleton thread-safe plus simple utilisant l'initialisation statique :

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);
}

Cette approche est thread-safe car le CLR garantit que l'initialisation des champs statiques ne se produit qu'une seule fois.

Utilisation - retourne toujours la même instance :

Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");

Essayez vous-même

using System;
using Configuration;

class Program
{
    public static void Main(string[] args)
    {
        // Lire l'entrée - le nouveau nom de l'application
        string newAppName = Console.ReadLine();
        
        // TODO: Obtenir l'instance de ConfigManager et afficher le nom de l'application par défaut
        
        // TODO: Obtenir à nouveau l'instance (dans une variable différente) et l'utiliser pour définir le nouveau nom de l'application
        
        // TODO: Obtenir l'instance une troisième fois et afficher le nom de l'application pour prouver que toutes les références partagent les mêmes données
    }
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet