首页 > 其他分享 >单例模式

单例模式

时间:2024-02-26 15:45:33浏览次数:20  
标签:初始化 Lazy Singleton 模式 实例 单例 加载

简介

单例模式是一种常见的设计模式,用于确保类只有一个实例,并提供一个全局访问点。以下是一个简单的单例模式的示例

双重检查锁定

经典的双重检查锁定是一种常见的在多线程环境下延迟初始化对象的方式。下面是一个使用双重检查锁定的单例模式的示例代码:

using System;

public sealed class Singleton
{
    private static volatile Singleton instance; // 使用 volatile 关键字确保 instance 变量在多线程环境下的可见性
    private static object syncRoot = new Object(); // 用于加锁的对象

    private Singleton() { }

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

    // 其他成员方法
    public void SomeMethod()
    {
        Console.WriteLine("Singleton method called.");
    }
}

 

在这个示例中,Singleton 类的实例被声明为 private static volatile Singleton instance

volatile 关键字确保当 instance 被初始化为实例时,对所有线程可见。

syncRoot 对象被用作互斥锁,以确保只有一个线程可以访问临界区代码。

Instance 属性中,使用双重检查锁定确保在实例不存在时才创建实例。

这种方式既保证了延迟加载又确保了线程安全。

优点:

  1. 延迟加载(Lazy Initialization):双重检查锁定允许对象在第一次被使用时进行初始化,而不是在程序启动时就创建。这可以节省资源,因为对象在需要时才会被创建。

  2. 线程安全:通过双重检查,确保了在多线程环境下只有一个实例被创建。这是通过在第一次检查前后使用锁来实现的,因此在多线程情况下也能正常工作。

缺点:

  1. 性能开销:在每次访问实例时都需要进行双重检查和加锁操作,这会带来一定的性能开销。虽然通过双重检查可以减少锁的竞争,但在高并发环境下仍然可能会存在性能问题。

  2. 可能的重排序问题:一些编程语言和编译器可能会对代码进行指令重排序,这可能会导致双重检查锁定失效。为了解决这个问题,需要使用 volatile 关键字来确保指令不会被重排序,

  3. 但是在某些平台上 volatile 语义可能不够强大,无法完全解决问题。

静态初始化

静态初始化是一种在 C# 中实现单例模式的方式,它利用类的静态构造函数在类加载时就创建单例实例。以下是使用静态初始化实现单例模式的示例代码:

public sealed class Singleton
{
    // 在类加载时就创建实例,保证线程安全
    private static readonly Singleton instance = new Singleton();

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

    // 公有静态属性,提供全局访问点
    public static Singleton Instance => instance;

    // 其他成员方法
    public void SomeMethod()
    {
        Console.WriteLine("Singleton method called.");
    }
}

在这个示例中,Singleton 类的实例 instance 被声明为 private static readonly,这保证了在类加载时就会被创建,并且只会被创建一次。由于静态构造函数只会在类加载时被调用一次,因此这种方式可以保证单例的线程安全性。

由于 instancereadonly 的,它只能在静态构造函数中被赋值,且只能赋值一次,这就确保了单例实例的唯一性。

通过公有的静态属性 Instance 提供了对单例实例的全局访问点。其他代码可以通过 Singleton.Instance 来获取单例实例,并调用其方法。

这种方式简单直接,且保证了线程安全性,是一种常见的实现单例模式的方式。

优点:

  1. 线程安全:静态初始化保证了在类加载时就创建单例实例,并且由于静态构造函数只会在类加载时被调用一次,因此在多线程环境下也能保证单例实例的唯一性。

  2. 简单明了:相比于其他实现方式,静态初始化的实现方式非常简单,只需要在类中声明一个静态字段,并在静态构造函数中初始化实例即可。

  3. 延迟加载:虽然是在类加载时就创建了实例,但由于静态构造函数只有在实例被请求时才会被调用,因此也可以看作是一种延迟加载的方式。

缺点:

  1. 无法实现懒加载:虽然可以看作是一种延迟加载,但实际上单例实例是在类加载时就被创建了。因此,如果应用程序中并不总是需要这个单例实例,静态初始化就会带来不必要的资源消耗。

  2. 无法处理异常:如果在静态构造函数中出现异常,那么该异常将无法被捕获,也无法通过其他方式处理。这可能导致程序的不稳定性。

  3. 不支持延迟初始化选项:由于实例是在类加载时就被创建的,因此无法通过其他方式控制实例的初始化时间,也无法实现延迟初始化。

  4. 不支持依赖注入:静态初始化方式下,单例实例的创建是在类的静态构造函数中进行的,无法通过构造函数参数等方式实现依赖注入,因此不太适合需要依赖注入的场景

使用 Lazy<T> 类

使用 Lazy<T> 类可以实现延迟加载的单例模式,同时确保线程安全。这是一种简洁而且安全的方式,适用于大多数情况。以下是使用 Lazy<T> 类的范式示例:

using System;

public sealed class Singleton
{
    // 使用 Lazy<T> 类确保延迟加载和线程安全
    private static readonly Lazy<Singleton> lazyInstance = new Lazy<Singleton>(() => new Singleton());

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

    // 公有静态属性,提供全局访问点
    public static Singleton Instance => lazyInstance.Value;

    // 其他成员方法
    public void SomeMethod()
    {
        Console.WriteLine("Singleton method called.");
    }
}
在这个示例中,Singleton 类的实例 lazyInstance 被声明为 private static readonly Lazy<Singleton>,它使用了 Lazy<T> 类,并在初始化时传递了一个匿名方法,以确保在需要时才会创建实例。
由于 Lazy<T> 类在其内部使用了线程安全的延迟初始化技术,因此它保证了在多线程环境下只有一个实例被创建,并且只会在需要时才会被创建,从而实现了延迟加载的效果。
通过公有的静态属性 Instance 提供了对单例实例的全局访问点,其他代码可以通过 Singleton.Instance 来获取单例实例,并调用其方法。
这种方式简洁而且安全,是一种常见的实现单例模式的方式。

优点:

  1. 延迟加载:Lazy<T> 类能够延迟初始化单例实例,即在第一次访问时才会创建实例,从而节省了资源。

  2. 线程安全:Lazy<T> 类内部使用了线程安全的延迟初始化技术,确保在多线程环境下只有一个实例被创建。

  3. 简洁性:使用 Lazy<T> 类实现单例模式的代码相对较少,逻辑清晰,易于理解。

  4. 性能优化:Lazy<T> 类在实现上考虑了性能优化,避免了不必要的锁竞争,提高了并发访问性能。

缺点:

  1. 额外开销:虽然 Lazy<T> 类能够延迟初始化单例实例,但在实例第一次被访问时会增加一些额外的开销,包括委托的创建、初始化等。

  2.  不支持配置选项:Lazy<T> 类的初始化参数相对较少,无法支持更多的配置选项,比如依赖注入等。因此,如果单例类需要依赖注入,可能不太适合使用 Lazy<T> 类。

  3.  不够灵活:Lazy<T> 类适用于大多数情况,但在一些特定场景下可能不够灵活,比如需要自定义初始化行为、需要使用其他同步机制等。

  4.  可序列化问题:如果单例类需要支持序列化和反序列化,需要格外注意 Lazy<T> 类的序列化行为,因为它的实现是基于委托和延迟加载的。

 

标签:初始化,Lazy,Singleton,模式,实例,单例,加载
From: https://www.cnblogs.com/mchao/p/18034436

相关文章

  • 面向对象,到底是个什么鬼? (设计模式)
    什么才算是面向对象编程语言面向对象是支持类对性得语法机制,并有现成得语法机制,能方便得实现面向对象得封装,继承多态,抽象。 一般来讲,面型对象编程,是通过面向对像得编程语言来进行得,但是不用面型对象编程语言,我们照样可以进行面向对象编程,反过来讲,即使我们使用面向像得语言写出......
  • 那些维度评价代码的好坏?设计模式
    1.可维护性对于项目来说,维护代码的耗时,远远大于大于代码的编码。代码维护性非常关键主观评价标准:bug容易修复,添加功能比较简单。2.可读性代码的可读性,关乎代码的可维护性。 代码是否符合代码的命名规范。 命名是否规范,注释是否全面,函数是否长短合适,模块划分是否清......
  • 享元模式
    享元模式(flyweightpattern)定义: 摈弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,从而让我们能在优先的内存容量中载入更多的对象。 从这个定义可以发现,享元模式要解决的核心问题就是节约内存空间,使用的办法是找出相似对象之间的共有特征,然后复用这些特......
  • 在K8S中,kube-proxy的工作模式是什么?
    kube-proxy在Kubernetes集群中负责实现Service的网络代理和负载均衡功能,支持三种不同的工作模式:Userspace模式(已过时):在早期的Kubernetes版本中(1.2之前),kube-proxy默认使用Userspace模式。在此模式下,kube-proxy作为一个用户空间进程运行,为每个Service创建一个......
  • 优化通道颜色控制问题 - 使用状态模式
    在软件开发中,经常会遇到需要控制通道颜色的场景。如何优化通道颜色控制逻辑,提高代码的可维护性和扩展性呢?本篇博客将介绍如何使用状态模式来优化通道颜色控制逻辑。问题描述假设我们有一个需求:根据不同的通道状态来控制通道显示的颜色。通道状态包括正常状态、加热状态等。我们......
  • 类变量和类方法、代码块、单例设计模式、final关键字、抽象类、接口、内部类
    类变量和类方法类变量-提出问题说:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?,编写程序解决。传统的方法来解决思路在main方法中定义一个变量count当一个小孩加入游戏后count++,最后个count就记录有多少小孩玩游戏小孩是一个类,有名字属......
  • 01 单例模式
    usingUnityEngine;publicclassSingleton:MonoBehaviour{////饿汉式单例////privatestaticSingleton_instance=new();//privatestaticSingleton_instance;//publicstaticSingletonInstance=>_instance;//privatevoidAwake()=&g......
  • 02 状态模式
    usingUnityEngine;///<summary>///功能说明:状态模式吃睡干例子///</summary>publicclassDPState:MonoBehaviour{privatevoidStart(){Contextcontext=new();context.SetState(newSleep(context));context.Handle();......
  • 什么时候用静态方法?什么时候用单例模式
    什么时候用静态方法?什么时候用单例模式我们在编程中最常用的模式就是单例模式了,然而单例模式都用在什么场合?为什么不用静态方法而要用单例模式呢?要搞清这些问题,需要从静态方法和非静态方法的区别和联系说起。一、静态方法常驻内存,非静态方法只有使用的时候才分配内存?一般都认......
  • 依赖注入(Dependency Injection, DI)是一种设计模式,例如,在React中,父组件可以通过props向
    依赖注入renderprops其实就是React世界中的“依赖注入”(DependencyInjection)。所谓依赖注入,指的是解决这样一个问题:逻辑A依赖于逻辑B,如果让A直接依赖于B,当然可行,但是A就没法做得通用了。依赖注入就是把B的逻辑以函数形式传递给A,A和B之间只需要对这个函数......