【读书活动感悟分享】特能扛——《设计模式:可复用面向对象软件的基础》第3章 创建型模式 - 3.5 SINGLETON(单件)_文章

【读书活动感悟分享】特能扛——《设计模式:可复用面向对象软件的基础》第3章 创建型模式 - 3.5 SINGLETON(单件)

张建利
发表于 2025-11-10 17:29:54

一、概述定义

定义:

确保一个类只有一个实例,并提供全局访问点以获取该实例。它是一种创建型模式,通常用于需要严格控制某个类的实例数量的情况。

目的:

单例模式确保一个类在整个应用程序生命周期中只有一个实例,可以节省系统资源。

关键点:

  • 单一实例:单例模式确保一个类只有一个实例对象存在。
  • 全局访问点:单例模式提供了一个全局的访问点,其他对象可以通过该访问点获取单例实例。
  • 延迟加载(可选):在需要时才进行单例对象的创建,可以减少应用程序启动时的资源占用。
  • 线程安全性(可选):在多线程环境下,单例模式需要考虑线程安全性,以确保只有一个实例被创建。



二、应用场景

  • 全局状态管理:如配置信息、全局计数器、日志记录器等。
  • 资源共享:如数据库连接池、线程池等。
  • 控制并发访问:如实现全局锁、读写锁等。

三、优缺点

优点

  • 全局唯一实例:确保一个类只有一个实例,避免重复创建对象,节省内存。
  • 控制资源访问:通过单例模式,可以集中管理对共享资源的访问,防止资源滥用。
  • 简化全局状态管理:在需要维护全局状态的应用中,单例模式提供了一种简单的方式来访问和修改这些状态。
  • 实现线程安全:可以通过适当的同步机制,确保单例在多线程环境下的唯一性和线程安全性。
  • 减少对象间依赖:由于只有一个实例,减少了对象间的依赖关系,使得代码更加简洁和易于维护。

缺点

  • 违背单一职责原则‌:单例模式通常将所有功能集中在一个类中,容易导致职责过重,违背单一职责原则‌。
  • 扩展困难‌:单例模式通常没有接口,扩展时需要修改原有代码,违背了开闭原则,扩展性较差‌。
  • 内存泄漏风险:如果单例对象持有大量资源且长时间不被垃圾回收,可能会导致内存泄漏。
  • 性能安全问题:在多线程环境中,需要额外的机制来保证单例的唯一性和线程安全性,会带来性能上的开销。

四、实现示例

饿汉式:

public class Singleton1
    {
///
/// 私有静态实例,初始化时创建
///
private static Singleton1 instance = new Singleton1( );
///
/// 私有构造函数,外部无法实例化
///
private Singleton1 ( ) { }
///
/// 公有静态方法,返回静态实例
///
///
public static Singleton1 GetInstance ( )
{
return instance;
}
///
/// 业务方法
///
public void DoSomething ( )
{
Console.WriteLine( "Do something" );
}
}

初始化时机:饿汉模式是在应用程序启动时就进行单例对象的初始化,无论是否会被使用。因此,单例对象在应用程序生命周期内都存在。

优点:

  • 不需要考虑多线程环境下的线程安全性,因为单例对象在应用程序启动时就已经创建。
  • 访问单例对象时不会引入额外的性能开销,因为它已经初始化。
  • 可能会浪费系统资源,因为单例对象在应用程序启动时就被创建,如果一直未被使用,可能会占用内存。
  • 不支持延迟加载,因为单例对象在应用程序启动时就已经初始化。

缺点:

  • 可能会浪费系统资源,因为单例对象在应用程序启动时就被创建,如果一直未被使用,可能会占用内存。
  • 不支持延迟加载,因为单例对象在应用程序启动时就已经初始化。

懒汉式:(线程不安全)

public class Singleton2
{
///
/// 私有静态实例,初始化时创建
///
private static Singleton2 instance;
///
/// 私有构造函数,外部无法实例化
///
private Singleton2 ( ) { }
///
/// 公有静态方法,返回静态实例
///
///
public static Singleton2 GetInstance ( ) {
if ( instance == null )
{
instance = new Singleton2( );
}
return instance;
}
///
/// 业务方法
///
public void DoSomething ( )
{
Console.WriteLine( "Do something" );
}
}

初始化时机:懒汉模式是延迟加载的,在首次访问时才进行初始化。在多线程环境中,可能会出现竞态条件,需要额外的线程安全措施来确保只创建一个实例。

优点:

  • 节省了系统资源,因为在应用程序启动时不会创建单例对象。
  • 可以实现延迟加载,只有在需要时才进行初始化。

缺点:

  • 在多线程环境下,需要考虑线程安全性,通常需要使用互斥锁等机制来保证单例对象的唯一性。
  • 首次访问单例对象时可能会引入额外的性能开销,因为需要进行初始化。
  • 此实现方式在多线程环境下可能导致多个实例被创建

懒汉式:(线程安全)

public class Singleton3
{
private static Singleton3 instance;
///
/// 锁对象
///
private static readonly object syncRoot = new object( );
private Singleton3 ( ) { }
public static Singleton3 GetInstance ( )
{
if ( instance == null )
{
// 双重锁定
lock ( syncRoot )
{
if ( instance == null )
{
instance = new Singleton3( );
}
}
}
return instance;
}
public void DoSomething ( )
{
Console.WriteLine( "Do something" );
}
}

特点:

第一次调用时才创建实例,方法同步,线程安全但效率低。

优点:

  • 延迟初始化:只有在第一次使用时才创建对象,节省了内存资源。
  • 线程安全:可以确保在多线程环境下只有一个线程能够创建实例对象,从而保证了线程安全。

缺点:

  • 性能开销:每次访问单例对象时都需要进行同步操作,可能会带来一定的性能开销。
  • 实现复杂:为了保证线程安全,需要采用加锁等机制。增加了实现的复杂性,并可能导致代码难以理解和维护。

懒汉式:(静态内部类)

public class Singleton5
{
///
/// 私有构造函数,防止外部实例化
///
private Singleton5 ( ) { }
///
/// 公有静态方法,提供全局访问点
///
/// 单例实例
public static Singleton5 GetInstance ( )
{
// 通过静态内部类获取单例实例
return Nested.instance;
}
///
/// 静态内部类,负责实例的延迟初始化
///
private class Nested
{
// 静态构造函数,确保类型初始化时只执行一次
static Nested ( ) { }
// 静态只读实例,保证唯一性和线程安全
internal static readonly Singleton5 instance = new Singleton5( );
}
///
/// 业务方法
///
public void DoSomething ( )
{
Console.WriteLine( "Do something" );
}
}

优点:

  • 延迟加载:实例在第一次使用时才会被创建,避免了不必要的资源消耗。
  • 线程安全:利用CLR的类型初始化机制,保证了线程安全性,无需额外的同步锁。
  • 实现简单:代码简洁明了,易于理解和实现。

缺点:

  • 不支持参数化实例:无法传递参数来初始化实例。
  • 不适用于需要频繁创建和销毁实例场景:单例模式本身的限制。

懒汉式:(Lazy<T>)

public class Singleton6
{
///
/// 使用Lazy类型实现延迟初始化和线程安全
///
private static readonly Lazy lazy = new Lazy( ( ) => new Singleton6( ) );
///
/// 私有构造函数,防止外部实例化
///
private Singleton6 ( ){}
///
/// 公有静态方法,提供全局访问点
///
/// 单例实例
public static Singleton6 GetInstance ( )
{
return lazy.Value;
}
///
/// 业务方法
///
public void DoSomething ( )
{
Console.WriteLine( "Do something" );
}
}

优点

  • 延迟加载:实例在第一次使用时才会被创建,避免了不必要的资源消耗。
  • 线程安全:Lazy<T>类型默认是线程安全的,确保实例的唯一性。
  • 实现简单:代码简洁明了,易于理解和实现。

缺点

  • 不支持参数化实例:无法传递参数来初始化实例。
  • 依赖于Lazy<T>类型:需要了解和使用Lazy<T>类型。

五、总结思考

  • 核心思想:确保一个类只有一个实例,并提供全局访问点。
  • 实现方式:多种实现方式,各有优缺点。
  • 应用场景:适用于需要全局唯一实例的场景。

91 0

评论


意见反馈