线程安全单例模式

一、什么是单例设计模式中的线程安全?

首先,创建一个单例类:

public sealed class GuidService
{
    private static int counter = 0;
    private static GuidService? _guidService = null;
    public static GuidService GetGuidService
    {
        get
        {
            if (_guidService is null)
                _guidService = new GuidService();
            return _guidService;
        }
    }

    private GuidService()
    {
        counter++;
        Console.WriteLine("Counter Value " + counter.ToString());
    }
    public void PrintDetails(string message)
    {
        Console.WriteLine(message);
    }
}

在主方法中修改调用方式,改为多线程环境,如下:

Parallel.Invoke(() =>
{
    GuidService guidService1 = GuidService.GetGuidService;
    guidService1.PrintDetails("guidService1");
},
() =>
{
    GuidService guidService2 = GuidService.GetGuidService;
    guidService2.PrintDetails("guidService2");
});
Console.ReadKey();

执行结果如下:

Counter Value 2
Counter Value 2
guidService1
guidService2

采用Parallel.Invoke()并行方法调用GetGuidService属性,因为我们在这里编写代码的方式是允许两个不同的线程可以同时计算条件 if (instance == null) ,并且两个线程都发现条件为真,它们都会创建实例,这违反了单例设计模式。

二、单例模式中的懒加载是什么?

在上边代码示例中,只有调用类的GetGuidService属性时,对象才会被创建,单例实例的延迟创建,就是懒加载,也就是惰性初始模式。这种方式在单线程环境中工作的很好,但是在多线程环境中,多个线程同时调用 GetGuidService 属性时,惰性初始模式最终可能会创建单例类的多个实例。

三、如何实现线程安全的单例设计模式?

可以使用C#中的Lock来控制多线程环境中的多个线程同时访问的情况,当然,还有其它方式,但是,锁是处理单例实例的最佳选项。

四、使用锁来实现线程安全的单例设计模式

使用锁,可以同步该方法。在任何给定的时间点只有一个线程可以访问它。在下面的代码中使用锁来锁定共享资源,当线程访问时,会先判断该共享资源是否已被锁定,如果条件成立,就表明内部已有线程访问,就无法进入内部执行创建实例代码,然后检查是否创建了实例。如果实例已经创建,那么只需返回该实例,否则创建该实例,然后返回该实例。代码如下:

public sealed class GuidService
{
    private static int counter = 0;
    private static GuidService? _guidService = null;
    private static readonly object Instancelock = new object();
    public static GuidService GetGuidService
    {
        get
        {
            lock (Instancelock)
            {
                if (_guidService is null)
                    _guidService = new GuidService();
                return _guidService;
            }
        }
    }

    private GuidService()
    {
        counter++;
        Console.WriteLine("Counter Value " + counter.ToString());
    }
    public void PrintDetails(string message)
    {
        Console.WriteLine(message);
    }
}

输出结果:

Counter Value 1
guidService1
guidService2

上面的代码实现使用锁解决了多线程问题。但问题是它会降低应用程序的速度,因为在任何给定的时间点,只有一个线程可以访问 GetInstance 属性,这样就会造成一些问题,比如,第一个线程已经创建对象成功,但是还未释放被锁对象,此时不管多少个线程访问,就会进入阻塞状态,而不会获取到被创建的对象。可以利用双重检查锁定模式机制解决上述问题,代码如下:

public sealed class GuidService
{
    private static int counter = 0;
    private static GuidService? _guidService = null;
    private static readonly object Instancelock = new object();
    public static GuidService GetGuidService
    {
        get
        {
            if (_guidService is null)
            {
                lock (Instancelock)
                {
                    if (_guidService is null)
                        _guidService = new GuidService();
                }
            }
            return _guidService;
        }
    }

    private GuidService()
    {
        counter++;
        Console.WriteLine("Counter Value " + counter.ToString());
    }
    public void PrintDetails(string message)
    {
        Console.WriteLine(message);
    }
}

在双重检查锁定模式机制中,当创建了一个实例之后,再次调用GetGuidService属性就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。

posted @ 2023-01-19 16:00  初晨~  阅读(122)  评论(0编辑  收藏  举报