一、什么是初始加载?

实现单例模式有两种方式,一种是懒加载,也就是延迟加载,当首次调用时创建单例对象,另一种是初始加载,在应用程序启动时就初始化单例对象,并将其保存在内存中以备将来使用,而不是需要时才创建。初始加载不需要考虑多线程环境导致的线程不安全问题,因为CLR将负责对象初始化和线程安全。这意味着我们不需要显式地编写任何代码来处理多线程环境的线程安全性。如下:

public sealed class Singleton
{
    private static int counter = 0;
    private static readonly Singleton singleInstance = new Singleton(); 
    private Singleton()
    {
        counter++;
        Console.WriteLine("Counter Value " + counter.ToString());
    }
    
    public static Singleton GetInstance
    {
        get
        {
            return singleInstance;
        }
    }
    public void PrintDetails(string message)
    {
        Console.WriteLine(message);
    }
}

定义私有静态常量singleInstance,并实例化Singleton对象赋值给该常量,由于是静态的,所以在应用启动就会创建,并加载到内存中,可以随时调用。主要由CLR在内部负责变量的初始化以及即时加载中的线程安全。

二、什么是延迟加载?

定义私有静态常量,但是并不实例化赋值,而是等到首次调用时再创建实例,往后每次调用就用这一个实例。也就是将对象的初始化延迟到需要它的时候。当创建对象的成本非常高以及很少使用该对象时,需要使用延迟加载。如果使用得当,延迟加载可以提高应用程序的性能。在C#中可以使用Lazy关键字使单例实例延迟加载。

三、理解C#中的Lazy关键字

Lazy是在.NET Framework 4.0引入的,提供对延迟初始化的支持。使用方式如下:

private static readonly Lazy<Singleton> Instancelock = new Lazy<Singleton>(() => new Singleton());

非常重要的一点是: Lazy<T> 对象在默认情况下是线程安全的。在多线程环境中,当多个线程试图同时访问相同的 GetInstance 属性时,Lazy将负责线程安全。代码如下:

public sealed class Singleton
{
    private static int counter = 0;
    private static readonly Lazy<Singleton> Instancelock 
        = new Lazy<Singleton>(() => new Singleton());

    private Singleton()
    {
        counter++;
        Console.WriteLine("Counter Value " + counter.ToString());
    }
    public static Singleton GetInstance
    {
        get
        {
            return Instancelock.Value;
        }
    }
    public void PrintDetails(string message)
    {
        Console.WriteLine(message);
    }
}

最后输出的结果和初始加载运行输出的结果是相同的,这是因为Lazy关键字只创建一个单例实例,而且它们在默认情况下是线程安全的。