c#-单例模式

单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取该实例。以下是详细介绍单例模式的各种实现方式。

1. 饿汉式单例模式

代码示例
复制代码
using System;

// 饿汉式单例类
public sealed class EagerSingleton
{
    // 静态只读字段,在类加载时就初始化实例
    private static readonly EagerSingleton instance = new EagerSingleton();

    // 私有构造函数,防止外部实例化
    private EagerSingleton()
    {
        Console.WriteLine("EagerSingleton 实例已创建");
    }

    // 公共静态属性,用于获取单例实例
    public static EagerSingleton Instance
    {
        get
        {
            return instance;
        }
    }

    // 示例方法
    public void DoSomething()
    {
        Console.WriteLine("EagerSingleton 正在执行操作");
    }
}

class Program
{
    static void Main()
    {
        // 获取单例实例
        EagerSingleton singleton = EagerSingleton.Instance;
        // 调用示例方法
        singleton.DoSomething();
    }
}
案例解读
  • 类加载与实例创建 :在 C# 中,当一个类被加载时,其静态成员会被初始化。这里的 private static readonly EagerSingleton instance = new EagerSingleton(); 使得 EagerSingleton 类在加载时就创建了唯一的实例。readonly 关键字保证了这个实例一旦被创建,就不能被重新赋值。
  • 私有构造函数private EagerSingleton() 阻止了外部代码通过 new 关键字创建新的 EagerSingleton 实例,确保了单例的唯一性。
  • 公共静态属性public static EagerSingleton Instance 提供了一个全局访问点,外部代码可以通过 EagerSingleton.Instance 来获取这个唯一的实例。
优缺点分析
  • 优点:实现简单,线程安全。由于实例在类加载时就创建好了,不存在多线程同时创建实例的问题。
  • 缺点:可能会造成资源浪费。如果这个单例实例在整个应用程序的生命周期中都没有被使用,那么它仍然会在类加载时被创建,占用系统资源。

2. 懒汉式单例模式(非线程安全)

代码示例
复制代码
using System;

// 懒汉式单例类(非线程安全)
public sealed class LazySingletonNonThreadSafe
{
    // 静态字段,初始为 null
    private static LazySingletonNonThreadSafe instance;

    // 私有构造函数,防止外部实例化
    private LazySingletonNonThreadSafe()
    {
        Console.WriteLine("LazySingletonNonThreadSafe 实例已创建");
    }

    // 公共静态属性,用于获取单例实例
    public static LazySingletonNonThreadSafe Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new LazySingletonNonThreadSafe();
            }
            return instance;
        }
    }

    // 示例方法
    public void DoSomething()
    {
        Console.WriteLine("LazySingletonNonThreadSafe 正在执行操作");
    }
}

class Program
{
    static void Main()
    {
        // 获取单例实例
        LazySingletonNonThreadSafe singleton = LazySingletonNonThreadSafe.Instance;
        // 调用示例方法
        singleton.DoSomething();
    }
}
案例解读
  • 延迟实例化private static LazySingletonNonThreadSafe instance; 初始化为 null,在第一次调用 Instance 属性时,通过 if (instance == null) 判断实例是否已经创建,如果未创建则创建新实例。这种方式实现了延迟加载,只有在需要使用实例时才会创建。
  • 非线程安全问题 :在多线程环境下,可能会有多个线程同时进入 if (instance == null) 这个判断语句,从而导致多个线程都创建了新的实例,破坏了单例的唯一性。
优缺点分析
  • 优点:实现了延迟加载,避免了不必要的资源浪费。
  • 缺点:非线程安全,在多线程环境下不能保证单例的唯一性。

3. 懒汉式单例模式(线程安全,简单加锁)

代码示例
复制代码
using System;

// 懒汉式单例类(线程安全,简单加锁)
public sealed class LazySingletonThreadSafeSimple
{
    // 静态字段,初始为 null
    private static LazySingletonThreadSafeSimple instance;
    // 静态对象,用于加锁
    private static readonly object lockObject = new object();

    // 私有构造函数,防止外部实例化
    private LazySingletonThreadSafeSimple()
    {
        Console.WriteLine("LazySingletonThreadSafeSimple 实例已创建");
    }

    // 公共静态属性,用于获取单例实例
    public static LazySingletonThreadSafeSimple Instance
    {
        get
        {
            lock (lockObject)
            {
                if (instance == null)
                {
                    instance = new LazySingletonThreadSafeSimple();
                }
            }
            return instance;
        }
    }

    // 示例方法
    public void DoSomething()
    {
        Console.WriteLine("LazySingletonThreadSafeSimple 正在执行操作");
    }
}

class Program
{
    static void Main()
    {
        // 获取单例实例
        LazySingletonThreadSafeSimple singleton = LazySingletonThreadSafeSimple.Instance;
        // 调用示例方法
        singleton.DoSomething();
    }
}
案例解读
  • 加锁机制private static readonly object lockObject = new object(); 创建了一个静态的锁对象。在 Instance 属性的 get 方法中,使用 lock (lockObject) 对代码块进行加锁,确保同一时间只有一个线程能进入这个代码块。
  • 线程安全实现 :通过加锁,当一个线程进入 lock 代码块时,其他线程会被阻塞,直到该线程完成实例的创建。这样就保证了在多线程环境下实例只会被创建一次。
优缺点分析
  • 优点:实现了线程安全,保证了单例的唯一性。
  • 缺点:每次获取实例都需要加锁,加锁操作会带来一定的性能开销。即使实例已经创建好了,后续的线程访问仍然需要进行加锁操作,这在高并发场景下会影响性能。

4. 双重检查锁定单例模式(线程安全)

代码示例
复制代码
using System;

// 双重检查锁定单例类
public sealed class DoubleCheckedLockingSingleton
{
    // 静态字段,初始为 null
    private static DoubleCheckedLockingSingleton instance;
    // 静态对象,用于加锁
    private static readonly object lockObject = new object();

    // 私有构造函数,防止外部实例化
    private DoubleCheckedLockingSingleton()
    {
        Console.WriteLine("DoubleCheckedLockingSingleton 实例已创建");
    }

    // 公共静态属性,用于获取单例实例
    public static DoubleCheckedLockingSingleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (lockObject)
                {
                    if (instance == null)
                    {
                        instance = new DoubleCheckedLockingSingleton();
                    }
                }
            }
            return instance;
        }
    }

    // 示例方法
    public void DoSomething()
    {
        Console.WriteLine("DoubleCheckedLockingSingleton 正在执行操作");
    }
}

class Program
{
    static void Main()
    {
        // 获取单例实例
        DoubleCheckedLockingSingleton singleton = DoubleCheckedLockingSingleton.Instance;
        // 调用示例方法
        singleton.DoSomething();
    }
}
案例解读
  • 第一次检查if (instance == null) 在加锁前先进行检查,如果实例已经创建好了,就直接返回实例,避免了不必要的加锁操作,提高了性能。
  • 加锁与第二次检查 :如果第一次检查发现实例为 null,则进入 lock (lockObject) 代码块进行加锁。在加锁后,再次进行 if (instance == null) 检查,防止多个线程同时通过第一次检查,在一个线程创建实例后,其他线程不会再重复创建。
优缺点分析
  • 优点:既实现了延迟加载,又保证了线程安全,同时减少了不必要的锁操作,提高了性能。
  • 缺点:实现相对复杂,需要对多线程编程有一定的理解。

5. 使用 Lazy<T> 实现单例模式(线程安全)

代码示例
复制代码
using System;

// 使用 Lazy<T> 实现的单例类
public sealed class LazyGenericSingleton
{
    // 静态只读的 Lazy<T> 实例
    private static readonly Lazy<LazyGenericSingleton> lazy = new Lazy<LazyGenericSingleton>(() => new LazyGenericSingleton());

    // 私有构造函数,防止外部实例化
    private LazyGenericSingleton()
    {
        Console.WriteLine("LazyGenericSingleton 实例已创建");
    }

    // 公共静态属性,用于获取单例实例
    public static LazyGenericSingleton Instance
    {
        get
        {
            return lazy.Value;
        }
    }

    // 示例方法
    public void DoSomething()
    {
        Console.WriteLine("LazyGenericSingleton 正在执行操作");
    }
}

class Program
{
    static void Main()
    {
        // 获取单例实例
        LazyGenericSingleton singleton = LazyGenericSingleton.Instance;
        // 调用示例方法
        singleton.DoSomething();
    }
}
案例解读
  • Lazy<T> 类的使用private static readonly Lazy<LazyGenericSingleton> lazy = new Lazy<LazyGenericSingleton>(() => new LazyGenericSingleton()); 创建了一个 Lazy<T> 实例,使用委托 () => new LazyGenericSingleton() 指定了实例的创建方式。
  • 延迟加载与线程安全Lazy<T> 类会在第一次访问 lazy.Value 时才创建实例,实现了延迟加载。同时,Lazy<T> 类内部处理了线程安全问题,确保实例只被创建一次。
优缺点分析
  • 优点:实现简单,代码简洁,同时保证了延迟加载和线程安全。
  • 缺点 :依赖于 .NET 框架的 Lazy<T> 类,如果需要在不支持该类的环境中使用,就无法采用这种实现方式。

6. 静态内部类实现单例模式(线程安全)

代码示例
复制代码
using System;

// 静态内部类实现的单例类
public sealed class StaticNestedClassSingleton
{
    // 私有构造函数,防止外部实例化
    private StaticNestedClassSingleton()
    {
        Console.WriteLine("StaticNestedClassSingleton 实例已创建");
    }

    // 静态内部类
    private static class Nested
    {
        // 静态只读字段,保存单例实例
        internal static readonly StaticNestedClassSingleton Instance = new StaticNestedClassSingleton();
    }

    // 公共静态属性,用于获取单例实例
    public static StaticNestedClassSingleton Instance
    {
        get
        {
            return Nested.Instance;
        }
    }

    // 示例方法
    public void DoSomething()
    {
        Console.WriteLine("StaticNestedClassSingleton 正在执行操作");
    }
}

class Program
{
    static void Main()
    {
        // 获取单例实例
        StaticNestedClassSingleton singleton = StaticNestedClassSingleton.Instance;
        // 调用示例方法
        singleton.DoSomething();
    }
}
案例解读
  • 静态内部类的特性 :在 C# 中,静态内部类只会在第一次被使用时加载。private static class Nested 是一个静态内部类,其中的 internal static readonly StaticNestedClassSingleton Instance = new StaticNestedClassSingleton(); 在类加载时创建了 StaticNestedClassSingleton 的实例。
  • 延迟加载与线程安全 :当第一次访问 StaticNestedClassSingleton.Instance 时,会触发 Nested 类的加载,从而创建单例实例。由于静态类的加载是线程安全的,所以这种方式实现了延迟加载和线程安全。
优缺点分析
  • 优点:实现简单,代码简洁,同时保证了延迟加载和线程安全。
  • 缺点:对于不熟悉静态内部类特性的开发者来说,可能不太容易理解。

7. 通过 Mutex 实现单例模式(系统级单例)

代码示例
复制代码
using System;
using System.Threading;

// 通过 Mutex 实现的单例类
public sealed class MutexSingleton
{
    // 静态字段,保存单例实例
    private static MutexSingleton instance;
    // 静态 Mutex 实例
    private static readonly Mutex mutex = new Mutex(true, "MutexSingleton");

    // 私有构造函数,防止外部实例化
    private MutexSingleton()
    {
        Console.WriteLine("MutexSingleton 实例已创建");
    }

    // 公共静态属性,用于获取单例实例
    public static MutexSingleton Instance
    {
        get
        {
            if (instance == null)
            {
                mutex.WaitOne();
                try
                {
                    if (instance == null)
                    {
                        instance = new MutexSingleton();
                    }
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }
            return instance;
        }
    }

    // 示例方法
    public void DoSomething()
    {
        Console.WriteLine("MutexSingleton 正在执行操作");
    }
}

class Program
{
    static void Main()
    {
        // 获取单例实例
        MutexSingleton singleton = MutexSingleton.Instance;
        // 调用示例方法
        singleton.DoSomething();
    }
}
案例解读
  • Mutex 的使用private static readonly Mutex mutex = new Mutex(true, "MutexSingleton"); 创建了一个命名的 Mutex 实例,true 表示创建线程拥有互斥体的初始所有权,"MutexSingleton" 是互斥体的名称,确保在整个系统中具有唯一性。
  • 线程同步与实例创建 :在 Instance 属性的 get 方法中,mutex.WaitOne() 等待获取互斥量,如果获取到则进入临界区。在临界区内,再次检查实例是否为 null,如果为 null 则创建实例。最后,使用 mutex.ReleaseMutex() 释放互斥量,允许其他线程或进程获取该互斥量。
优缺点分析
  • 优点:可以确保在整个系统中只有一个实例运行,常用于确保应用程序只有一个进程实例。
  • 缺点Mutex 是一个系统级的同步原语,使用它会带来一定的性能开销。而且,如果互斥量被异常占用,可能会导致程序出现死锁等问题。
相关推荐
为什么要内卷,摆烂不香吗几秒前
【无标题】
开发语言·php
江沉晚呤时5 分钟前
深入解析 C# 中的装饰器模式(Decorator Pattern)
java·开发语言·javascript·jvm·microsoft·.netcore
vvilkim16 分钟前
Vue.js 中的 Tree Shaking:优化你的应用性能
前端·javascript·vue.js
Front_Yue32 分钟前
Three.js中的加载器与资源管理:构建丰富3D场景的关键
javascript·3d·three.js
努力的飛杨1 小时前
学习记录-js进阶-性能优化
开发语言·javascript·学习
星零零1 小时前
【Java】链表(LinkedList)(图文版)
java·开发语言·数据结构·经验分享·笔记·链表
前端阿呆1 小时前
最全前端性能优化合集
前端·javascript
天天扭码1 小时前
JavaScript面试必杀技:电话号码格式化从入门到精通
前端·javascript·面试
天天扭码1 小时前
JavaScript变量提升完全指南:从表象到底层原理
前端·javascript·面试
海底火旺1 小时前
深入理解JavaScript的变量提升与执行机制:一段令人困惑的代码解密
javascript