Singleton Thread-Safe
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 52 de 70.
El patrón Singleton asegura que una clase tenga una única instancia en toda la aplicación y proporciona un punto de acceso global a ella. Esto es útil para gestionar recursos compartidos como ajustes de configuración, servicios de registro o conexiones a bases de datos.
Un Singleton básico utiliza un constructor privado para evitar la instanciación externa y una propiedad estática para proporcionar acceso:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Constructor privado
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}La sentencia lock garantiza la seguridad de hilos (thread safety): si varios hilos intentan acceder a Instance simultáneamente, solo uno puede entrar en la sección crítica a la vez. Sin esto, dos hilos podrían ver _instance como nulo y crear instancias separadas.
C# ofrece un enfoque más sencillo y seguro para hilos (thread-safe) utilizando la inicialización 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 versión es segura para subprocesos porque el CLR garantiza que la inicialización de campos estáticos ocurra solo una vez. Ambos enfoques aseguran que siempre obtengas la misma instancia:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Desafío
FácilVamos a construir un gestor de configuración seguro para hilos (thread-safe) utilizando el patrón Singleton. En las aplicaciones reales, los ajustes de configuración deben ser accesibles en todo el programa, garantizando al mismo tiempo que solo exista una instancia; un caso de uso perfecto para el Singleton.
Organizarás tu código en dos archivos:
ConfigManager.cs: Crea una claseConfigManageren el espacio de nombresConfigurationque implemente el patrón Singleton. Tu gestor de configuración debe:- Tener un campo estático privado para mantener la instancia única
- Usar un constructor privado para evitar la instanciación externa
- Proporcionar una propiedad estática
Instanceque devuelva la instancia única (utiliza el enfoque de inicialización estática más simple para la seguridad de hilos) - Incluir un campo de cadena privado
_appNameinicializado como"MyApp" - Tener un método
SetAppName(string name)para actualizar el nombre de la aplicación - Tener un método
GetAppName()que devuelva el nombre actual de la aplicación
Program.cs: En tu archivo principal, demuestra que el Singleton funciona correctamente accediendo a la instancia varias veces y mostrando que los cambios persisten en todas las referencias.
Recibirás una entrada:
- Un nuevo nombre de aplicación para configurar (por ejemplo,
ProductionServer)
En tu programa:
- Obtén la instancia de
ConfigManagere imprime el nombre de la aplicación por defecto - Obtén la instancia de nuevo (en una variable diferente) y úsala para establecer el nuevo nombre de la aplicación desde la entrada
- Obtén la instancia por tercera vez e imprime el nombre de la aplicación para demostrar que todas las referencias comparten los mismos datos
Por ejemplo, si la entrada es ProductionServer, la salida debería ser:
MyApp
ProductionServerEsto demuestra el beneficio principal del Singleton: no importa cuántas veces accedas a Instance, siempre obtendrás el mismo objeto con estado compartido. El enfoque de inicialización estática garantiza la seguridad de hilos porque el CLR asegura que el campo estático se inicialice solo una vez, incluso si varios hilos acceden a él simultáneamente.
Hoja de referencia
El patrón Singleton garantiza que una clase tenga una sola instancia en toda la aplicación y proporciona un punto de acceso global a ella.
Singleton básico con bloqueo explícito para seguridad de hilos (thread safety):
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Private constructor
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}La sentencia lock garantiza la seguridad de hilos al permitir que solo un hilo entre en la sección crítica a la vez.
Singleton seguro para hilos más simple utilizando inicialización 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);
}Este enfoque es seguro para hilos porque el CLR garantiza que la inicialización de los campos estáticos ocurra solo una vez.
Uso - siempre devuelve la misma instancia:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Pruébalo tú mismo
using System;
using Configuration;
class Program
{
public static void Main(string[] args)
{
// Leer entrada: el nuevo nombre de la aplicación
string newAppName = Console.ReadLine();
// TODO: Obtener la instancia de ConfigManager e imprimir el nombre de la aplicación por defecto
// TODO: Obtener la instancia de nuevo (en una variable diferente) y usarla para establecer el nuevo nombre de la aplicación
// TODO: Obtener la instancia por tercera vez e imprimir el nombre de la aplicación para demostrar que todas las referencias comparten los mismos datos
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes