[Patrón de diseño creativo] Patrón de diseño C#, patrón singleton.

题目:设计一个日志管理器(LogManager)的单例模式。要求在整个应用程序中只能存在一个日志管理器实例,通过该实例可以记录日志信息并输出到控制台。

要求:

LogManager类只能有一个实例,并提供全局访问点来获取该实例。

LogManager类在第一次被访问时进行初始化,并记录当前时间作为日志的开始时间。

LogManager类应该提供一个公有的方法来记录日志信息。每条日志信息包含日志时间和日志内容。

LogManager类应该提供一个公有的方法来输出所有日志信息到控制台。

LogManager类的实现应该是线程安全的,也就是说多个线程同时访问该类时,不会出现竞争条件。

LogManager类的代码应该具有良好的可读性和可维护性。

提示:

可以使用静态变量和静态构造函数来实现单例模式。

可以使用线程锁(Monitor)来实现线程安全性。

可以使用队列(Queue)来存储日志信息,在输出时按照先进先出的顺序输出。

Código de referencia:

using System.Collections.Concurrent;


namespace 单例
{
    
    
    //sealed全局只有一个实例 防止类被继承
    internal sealed class LogManager
    {
    
    
        //volatile 修饰后的变量  如果修改  可以被其他线程知晓
        private static volatile LogManager instance;
        private static object syncRoot = new object();
        private ConcurrentQueue<string> logQueue;
        private DateTime startTime;

        private LogManager() 
        {
    
    
            logQueue= new ConcurrentQueue<string>();
            startTime= DateTime.Now;
        }

        public static LogManager Instance {
    
     
        get {
    
    
                if (instance == null) 
                {
    
    
                    lock (syncRoot)
                    {
    
    
                        
                        if (instance == null) 
                        {
    
    
                            instance = new LogManager();
                        }
                    
                    }
                }
                return instance; 
            }
    
        }

        public void Log(string message) 
        {
    
    
            string log = $"{
      
      DateTime.UtcNow}:{
      
      message}";
            logQueue.Enqueue(log);
        }

        public void PrintLogs() 
        {
    
    
            while (logQueue.TryDequeue(out string log))
            {
    
    
                Console.WriteLine(log);
                System.Threading.Thread.Sleep(200);
            }

        }



    }
}


sealedLa palabra clave se utiliza para modificar una clase para indicar que la clase no se puede heredar. Cuando queremos que solo exista una instancia de una clase en toda la aplicación, podemos usar el patrón singleton y marcar la clase singleton sealedpara evitar que otras clases la hereden y creen múltiples instancias.

volatileLas palabras clave se utilizan para modificar campos o variables, lo que indica que el campo o variable es visible en un entorno de subprocesos múltiples y puede modificarse en cualquier momento. En un programa de subprocesos múltiples, cada subproceso tiene su propio caché de subprocesos privado. Cuando un subproceso modifica un campo o variable, si no se modifica con volatilepalabras clave, es posible que otros subprocesos no vean el cambio inmediatamente porque todavía están usando su propio caché de subprocesos. . Cuando se utiliza volatilela palabra clave para modificar un campo o variable, se garantiza que las operaciones de lectura y escritura en el campo o variable se realicen directamente en la memoria principal, lo que garantiza visibilidad y coherencia.

En el código de ejemplo anterior:

sealedLas palabras clave se utilizan para modificar LogManageruna clase y evitar que otras clases la hereden.
volatileLa palabra clave se utiliza para modificar el campo para garantizar que la lectura y escritura se realicen desde la memoria principal instanceen un entorno multiproceso , evitando problemas causados ​​por las condiciones de carrera. Tenga en cuenta que el uso de la palabra clave en la programación de subprocesos múltiples no siempre es suficiente para garantizar la seguridad de los subprocesos, especialmente en el caso de operaciones compuestas o necesidades de sincronización más complejas. En estos casos suele ser necesario utilizar herramientas de sincronización más potentes como sentencias, clases, etc.instance
volatilelockMonitorMutex

En la implementación del modo singleton, para garantizar que solo se cree una instancia, varios subprocesos pueden encontrar la if (instance == null)condición de juicio de pasar al mismo tiempo. Si dos subprocesos pasan la condición casi al mismo tiempo, ambos ingresarán al (lock)bloque de código del mutex, lo que podría causar que se creen múltiples instancias.

Para resolver este problema, utilice un mecanismo de bloqueo de doble verificación. Primero, sin la creación de una instancia, ambos subprocesos pasarán la primera if (instance == null)condición de juicio. Luego, (lock)asegúrese de que solo un subproceso pueda continuar la ejecución adquiriendo un bloqueo mutex. Más tarde, cuando el subproceso que adquirió el bloqueo ingresa al (lock)bloque de código del mutex, verifica nuevamente si la instancia está vacía, porque es posible que otro subproceso haya creado la instancia antes de que el subproceso anterior ingresara al mutex. Si la instancia todavía está vacía en la segunda verificación, entonces ese hilo será responsable de crear la instancia, asegurando que solo se cree y asigne una instancia al instancecampo.

El propósito de esto es evitar competencias de bloqueo innecesarias y mejorar el rendimiento. El primer if (instance == null)cheque puede regresar rápidamente sin esperar el bloqueo mutex. El mutex se adquiere solo cuando es necesario crear una instancia, lo que garantiza que solo un subproceso pueda crear la instancia. La segunda if (instance == null)verificación es para evitar que el hilo actual se bloquee y espere a que finalice el mutex antes de que otro hilo obtenga el mutex y cree una instancia. En este momento, es posible que haya sido creado por otros hilos, evitando la creación instancede múltiples instancias. pregunta.

En resumen, el mecanismo de bloqueo de doble verificación logra un equilibrio entre el rendimiento y la seguridad de los subprocesos al nullverificar el valor dos veces para garantizar que solo se cree una instancia y reducir la contención de bloqueo tanto como sea posible.

Nota:
Eliminar internalel modificador de acceso: si desea que se pueda acceder a las clases de otros espacios de nombres LogManager, cambie su modificador de acceso a public.

Modificar logQueuey startTimecomo campos de instancia: modifique estos dos campos de staticcampos de instancia para evitar introducir sincronización innecesaria en el contexto estático.

ConcurrentQueue<T>En su lugar, se utiliza Queue<T>:ConcurrentQueue<T>una cola segura para subprocesos, que puede evitar el bloqueo manual y acceder a la cola en paralelo.

Úselo DateTime.UtcNowen su lugar para obtener marcas de tiempo con mayor precisión DateTime.Now:en un entorno de subprocesos múltiples .UtcNow

Supongo que te gusta

Origin blog.csdn.net/csdn2990/article/details/131581571
Recomendado
Clasificación