在软件设计中,单例模式(Singleton Pattern)是一种常用的设计模式,确保一个类只有一个实例,并提供一个全局访问点。在不同的编程语言和环境中,单例模式的实现方式有所不同,以下是单例模式的 8种常见写法,每种写法的优缺点以及适用场景。

1. 饿汉式(Eager Initialization)

定义:在类加载时就创建单例对象,不管是否使用。由于在类加载时就初始化,所以线程安全。

代码示例:

public class Singleton
{
    // 在类加载时就实例化对象
    private static readonly Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() { }

    // 公共访问方法
    public static Singleton Instance
    {
        get { return instance; }
    }
}

优点

  • 线程安全,因为静态字段在类加载时就被初始化。
  • 实现简单。

缺点

  • 无法延迟加载,即使不使用该类的实例,也会在程序启动时创建实例,可能浪费内存。

适用场景

  • 单例对象的创建不依赖外部因素,且程序启动时就需要加载。

2. 懒汉式(Lazy Initialization)

定义:单例对象在第一次使用时创建,线程不安全,需要使用额外的同步机制来保证线程安全。

代码示例:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}

优点

  • 延迟加载,只有在第一次访问时创建实例,节省内存。

缺点

  • 在多线程环境下,可能会出现线程安全问题。

适用场景

  • 需要延迟加载对象,但不考虑线程安全或在多线程中使用时没有额外的同步需求。

3. 线程安全的懒汉式(使用锁)

定义:在懒汉式基础上加入了同步机制,确保在多线程环境下的线程安全。

代码示例:

public class Singleton
{
    private static Singleton instance;
    private static readonly object lockObj = new object();

    private Singleton() { }

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

优点

  • 线程安全,确保在多线程环境中只有一个实例。

缺点

  • 由于加了锁,性能可能会有一定的影响,尤其是在高并发的情况下。

适用场景

  • 在多线程环境下使用懒汉式单例,需要确保线程安全。

4. 双重检查锁定(Double-Checked Locking)

定义:双重检查锁定优化了线程安全的懒汉式,通过减少锁的使用提高性能。

代码示例:

public class Singleton
{
    private static volatile Singleton instance;
    private static readonly object lockObj = new object();

    private Singleton() { }

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

优点

  • 线程安全,且避免了每次访问时都加锁的性能问题。
  • 在多线程环境中比普通懒汉式更高效。

缺点

  • 稍微复杂,使用 volatile 关键字确保可见性。
  • 并发较高时,锁的性能仍然是一个瓶颈。

适用场景

  • 在多线程环境下使用懒汉式,且需要优化性能。

5. 静态内部类(Bill Pugh Singleton)

定义:通过静态内部类实现单例模式,利用 Java 或 C# 的类加载机制保证线程安全,并且延迟加载。

代码示例:

public class Singleton
{
    private Singleton() { }

    private static class SingletonHelper
    {
        // 由于静态内部类在首次被访问时才会加载,所以可以确保延迟加载且线程安全
        public static readonly Singleton Instance = new Singleton();
    }

    public static Singleton Instance => SingletonHelper.Instance;
}

优点

  • 延迟加载和线程安全,由于静态内部类是类加载机制的一部分,所以实现简单且高效。
  • 不需要加锁,避免了锁带来的性能问题。

缺点

  • 需要使用静态类,代码稍显复杂。

适用场景

  • 推荐在多线程环境中使用此方法,性能优越。

6. 枚举单例(Effective Java 推荐)

定义:通过枚举实现单例模式,保证线程安全且防止反序列化导致的多例问题。

代码示例:

public enum Singleton
{
    Instance;

    public void DoSomething()
    {
        // 方法逻辑
    }
}

优点

  • 枚举类天生是线程安全的。
  • 防止反序列化和反射攻击,确保唯一性。

缺点

  • 代码不如其他方式直观。
  • 不适用于需要传参的场景。

适用场景

  • 适用于需要保证单例对象的应用,特别是在 Java 中非常推荐使用。

7. 通过依赖注入实现单例模式

定义:在依赖注入(DI)框架中,可以通过配置来确保单例模式。通常是在使用如 Spring、ASP.NET Core 等框架时,框架会自动管理实例的生命周期。

代码示例:

public class Singleton
{
    private Singleton() { }

    public void DoSomething()
    {
        // 做某些操作
    }
}

在 ASP.NET Core 中配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<Singleton>();
}

优点

  • 通过 DI 框架自动管理对象生命周期,简化代码。

缺点

  • 需要依赖 DI 容器,代码耦合性较高。

适用场景

  • 在使用 DI 框架时,如 Spring(Java)或 ASP.NET Core,适用于大规模应用。

8. 线程安全的懒加载(使用 Lazy<T>

定义:利用 C# 提供的 Lazy<T> 类型来实现线程安全的延迟加载单例。

代码示例:

public class Singleton
{
    private static readonly Lazy&lt;Singleton> lazy = new Lazy&lt;Singleton>(() => new Singleton());

    private Singleton() { }

    public static Singleton Instance => lazy.Value;
}

优点

  • 延迟加载,且线程安全。
  • 代码简洁,利用 C# 内置的 Lazy<T> 类型。

缺点

  • 依赖 C# 5.0 及以上版本,不适用于早期版本的 .NET Framework。

适用场景

  • 在现代 C# 应用中使用,简洁高效。

总结:

编号写法优点缺点适用场景
1饿汉式实现简单,线程安全无法延迟加载无需延迟加载的单例
2懒汉式延迟加载线程不安全单例延迟加载,但不考虑线程安全
3锁懒汉式线程安全性能较差需要线程安全的懒汉式
4双重检查锁定线程安全,性能较高稍复杂高并发且需要线程安全的懒汉式
5静态内部类线程安全,延迟加载,性能好稍显复杂多线程环境下的单例
6枚举单例线程安全,防止反序列化代码不直观需要强制唯一性,且无需传参的场景
7依赖注入由 DI 框架自动管理耦合度高,依赖框架使用 DI 容器的应用

8 | Lazy | 简洁,线程安全,延迟加载 | 仅适用于较新的 C# | C# 5.0 及以上版本的应用 |

不同的实现方式有不同的适用场景,可以根据具体的需求选择最合适的方式。