Потокобезопасный Singleton
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 52 из 70.
Паттерн Singleton гарантирует, что у класса есть только один экземпляр во всем приложении, и предоставляет глобальную точку доступа к нему. Это полезно для управления общими ресурсами, такими как настройки конфигурации, службы логирования или подключения к базе данных.
Базовый Singleton использует приватный конструктор для предотвращения создания экземпляров извне и статическое свойство для предоставления доступа:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Приватный конструктор
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}Оператор lock обеспечивает потокобезопасность — если несколько потоков попытаются получить доступ к Instance одновременно, только один сможет войти в критическую секцию в конкретный момент времени. Без этого два потока могли бы одновременно увидеть, что _instance равен null, и создать отдельные экземпляры.
C# предлагает более простой потокобезопасный подход с использованием статической инициализации:
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);
}Эта версия является потокобезопасной, так как CLR гарантирует, что инициализация статических полей происходит только один раз. Оба подхода гарантируют, что вы всегда получите один и тот же экземпляр:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Задание
ЛегкоДавайте создадим потокобезопасный менеджер конфигурации, используя паттерн Одиночка (Singleton). В реальных приложениях настройки конфигурации должны быть доступны во всей программе, при этом необходимо гарантировать существование только одного экземпляра — это идеальный случай использования Singleton.
Вы организуете свой код в двух файлах:
ConfigManager.cs: Создайте классConfigManagerв пространстве именConfiguration, который реализует паттерн Singleton. Ваш менеджер конфигурации должен:- Иметь закрытое статическое поле для хранения единственного экземпляра
- Использовать закрытый конструктор для предотвращения создания экземпляров извне
- Предоставлять статическое свойство
Instance, которое возвращает единственный экземпляр (используйте более простой подход со статической инициализацией для обеспечения потокобезопасности) - Включать закрытое строковое поле
_appName, инициализированное значением"MyApp" - Иметь метод
SetAppName(string name)для обновления названия приложения - Иметь метод
GetAppName(), который возвращает текущее название приложения
Program.cs: В основном файле продемонстрируйте правильную работу Singleton, обратившись к экземпляру несколько раз и показав, что изменения сохраняются во всех ссылках.
Вы получите один входной параметр:
- Новое название приложения для установки (например,
ProductionServer)
В вашей программе:
- Получите экземпляр
ConfigManagerи выведите название приложения по умолчанию - Получите экземпляр снова (в другую переменную) и используйте его для установки нового названия приложения из входных данных
- Получите экземпляр в третий раз и выведите название приложения, чтобы доказать, что все ссылки используют одни и те же данные
Например, если на вход подается ProductionServer, вывод должен быть следующим:
MyApp
ProductionServerЭто демонстрирует основное преимущество Singleton — сколько бы раз вы ни обращались к Instance, вы всегда получаете один и тот же объект с общим состоянием. Подход со статической инициализацией гарантирует потокобезопасность, так как CLR гарантирует, что статическое поле инициализируется только один раз, даже если несколько потоков обращаются к нему одновременно.
Шпаргалка
Паттерн Singleton (Одиночка) гарантирует, что у класса есть только один экземпляр во всем приложении, и предоставляет к нему глобальную точку доступа.
Базовая реализация Singleton с явной блокировкой для обеспечения потокобезопасности:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Приватный конструктор
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}Оператор lock обеспечивает потокобезопасность, позволяя только одному потоку входить в критическую секцию одновременно.
Более простой потокобезопасный Singleton с использованием статической инициализации:
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);
}Этот подход является потокобезопасным, так как CLR гарантирует, что инициализация статических полей происходит только один раз.
Использование — всегда возвращается один и тот же экземпляр:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Попробуйте сами
using System;
using Configuration;
class Program
{
public static void Main(string[] args)
{
// Считать ввод — новое имя приложения
string newAppName = Console.ReadLine();
// TODO: Получить экземпляр ConfigManager и вывести имя приложения по умолчанию
// TODO: Получить экземпляр снова (в другую переменную) и использовать его для установки нового имени приложения
// TODO: Получить экземпляр в третий раз и вывести имя приложения, чтобы доказать, что все ссылки используют одни и те же данные
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов