6 单例模式及其多线程问题

一、单例模式

单例模式可以保证一个类仅有一个实例,这个模式应该跟简单工厂一样常用了吧,但对我来说,以前都是瞎用,这是第一次深度学习单例模式。

最简单的单例模式代码是这样的:

复制代码
class Singleton
{
    private static Singleton _instance;
    private Singleton() { }

    public static  Singleton GetInstance()
    {
            if (_instance == null)
                _instance = new Singleton();
            return _instance;
    }
}
View Code
复制代码

 

核心思想是通过将构造函数设置成私有,并提供静态的公开方法来输出实例,这样就杜绝了外部多次实例化类的可能,在类内部保证了只产生一个实例。

二、多线程问题
上面的方法在多线程下会有漏洞,导致产生多个实例。比如三个线程同时执行到if (_instance == null)这一句,那么就会产生三个实例。这违背了我们的初衷。

在解决这个问题的方法上,有两种形式(lazy load和eager load)

1) lazy load

懒加载,在要用到类的实例的时候才实例化,前面的代码已经是lazy load模式了,但需要针对多线程做一些改进。在c#中,使用lock同步机制。如下:

复制代码
class LazyLoadSingleton
{
    private static LazyLoadSingleton _instance;
    private static readonly object syncRoot = new object();
    private LazyLoadSingleton() { }
    public static LazyLoadSingleton GetInstance()
    {
        lock (syncRoot)
        {
            if (_instance == null)
            {
                _instance = new LazyLoadSingleton();
            }
        }
        return _instance;
    }

}
View Code
复制代码

 

被lock包围的代码块成为了临界区,多个线程执行到lock语句的时候,将顺序执行。

这里有个细节需要注意,lock(synRoot),而不是_instance或this,因为_instance没实例的时候,这块会报错;this也无法在静态类使用,所以用了辅助对象synRoot。

 

然而,这种方法虽然解决了多个实例的问题,却带来了性能的瓶颈,因为每个行程到这儿都必须排队通过。那么继续改进,使用“双重检查加锁(double-checked locking)”:

复制代码
public static LazyLoadSingleton GetInstance()
{
    if (_instance == null)
    {
        lock (syncRoot)
        {
            if (_instance == null)
            {
                _instance = new LazyLoadSingleton();
            }
        }
    }
    return _instance;
}
View Code
复制代码

 

只有在初次实例化的时候才进行锁定,之后都直接return _instance;

这样便完美了,不得不感叹前辈们的创造力与执着啊!

2) eager load

“急切地”加载,即在类被加载的时候就将自身实例化:

复制代码
class EagerlyLoadSingleton
{
    private static readonly EagerlyLoadSingleton _instance = new EagerlyLoadSingleton();
    private EagerlyLoadSingleton() { }
    public static EagerlyLoadSingleton GetInstance()
    {
        return _instance;
    }
    public void Test()
    {
        Console.WriteLine("Test EagerlyLoadSingleton");
    }
}
View Code
复制代码

 

_instance被标记为readonly。

具体为什么这样,后续需要仔细学学readonly。

 

三、lazy load与eager load比较

lazy load在第一次被引用(调用GetInstance)的时候才实例化,eager load则在类被加载的时候就实例化。lazy load由于需要手动控制多线程下的实例化同步,代码比eager load复杂。最终,具体选用哪种方式根据实际应用场景决定,比如内存占用、调用频率等。

 

posted @   zhixin9001  阅读(274)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示