Menu
Coddy logo textTech

Singleton bezpieczny wątkowo

Część sekcji Programowanie obiektowe ścieżki C# w Coddy. Lekcja 52 z 70.

Wzorzec Singleton gwarantuje, że klasa ma tylko jedną instancję w całej aplikacji, i zapewnia globalny punkt dostępu do niej. Jest to przydatne do zarządzania współdzielonymi zasobami, takimi jak ustawienia konfiguracji, usługi rejestrowania zdarzeń czy połączenia z bazą danych.

Podstawowy wzorzec Singleton wykorzystuje prywatny konstruktor, aby uniemożliwić tworzenie instancji z zewnątrz, oraz właściwość statyczną, która zapewnia dostęp:

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

Instrukcja lock zapewnia bezpieczeństwo wątkowe — jeśli wiele wątków próbuje jednocześnie uzyskać dostęp do Instance, tylko jeden może w danej chwili wejść do sekcji krytycznej. Bez tego dwa wątki mogłyby jednocześnie stwierdzić, że _instance ma wartość null, i utworzyć oddzielne instancje.

C# oferuje prostsze podejście bezpieczne wątkowo, wykorzystujące inicjalizację statyczną:

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

Ta wersja jest bezpieczna wątkowo, ponieważ środowisko CLR gwarantuje, że inicjalizacja pola statycznego odbywa się tylko raz. Oba podejścia zapewniają, że zawsze otrzymujesz tę samą instancję:

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

Wyzwanie

Łatwy

Stwórzmy bezpieczny wątkowo menedżer konfiguracji, korzystając ze wzorca Singleton. W rzeczywistych aplikacjach ustawienia konfiguracji muszą być dostępne w całym programie, a jednocześnie musi istnieć tylko jedna instancja — to idealne zastosowanie wzorca Singleton.

Rozmieścisz kod w dwóch plikach:

  • ConfigManager.cs: Utwórz klasę ConfigManager w przestrzeni nazw Configuration, która implementuje wzorzec Singleton. Twój menedżer konfiguracji powinien:
    • Mieć prywatne pole statyczne przechowujące jedyną instancję
    • Używać prywatnego konstruktora, aby uniemożliwić tworzenie instancji z zewnątrz
    • Udostępniać statyczną właściwość Instance, która zwraca jedyną instancję (użyj prostszego podejścia ze statyczną inicjalizacją, aby zapewnić bezpieczeństwo wątkowe)
    • Zawierać prywatne pole tekstowe _appName zainicjalizowane wartością "MyApp"
    • Mieć metodę SetAppName(string name) do aktualizowania nazwy aplikacji
    • Mieć metodę GetAppName(), która zwraca bieżącą nazwę aplikacji
  • Program.cs: W pliku głównym pokaż, że Singleton działa poprawnie, uzyskując dostęp do instancji wielokrotnie i demonstrując, że zmiany są widoczne we wszystkich odwołaniach.

Otrzymasz jedno wejście:

  • Nowa nazwa aplikacji do ustawienia (np. ProductionServer)

W programie:

  1. Pobierz instancję ConfigManager i wypisz domyślną nazwę aplikacji
  2. Pobierz instancję ponownie (do innej zmiennej) i użyj jej do ustawienia nowej nazwy aplikacji z wejścia
  3. Pobierz instancję po raz trzeci i wypisz nazwę aplikacji, aby udowodnić, że wszystkie odwołania współdzielą te same dane

Na przykład, jeśli wejściem jest ProductionServer, wynikiem powinno być:

MyApp
ProductionServer

To pokazuje główną zaletę wzorca Singleton — bez względu na to, ile razy uzyskasz dostęp do Instance, zawsze otrzymasz ten sam obiekt ze współdzielonym stanem. Podejście ze statyczną inicjalizacją gwarantuje bezpieczeństwo wątkowe, ponieważ CLR zapewnia, że pole statyczne zostanie zainicjalizowane tylko raz, nawet jeśli jednocześnie uzyska do niego dostęp wiele wątków.

Spróbuj swoich sił

using System;
using Configuration;

class Program
{
    public static void Main(string[] args)
    {
        // Wczytaj dane wejściowe - nową nazwę aplikacji
        string newAppName = Console.ReadLine();
        
        // TODO: Pobierz instancję ConfigManager i wypisz domyślną nazwę aplikacji
        
        // TODO: Pobierz instancję ponownie (do innej zmiennej) i użyj jej do ustawienia nowej nazwy aplikacji
        
        // TODO: Pobierz instancję po raz trzeci i wypisz nazwę aplikacji, aby wykazać, że wszystkie odwołania współdzielą te same dane
    }
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C# online