单例模式

106 阅读5分钟

单例模式(Singleton Pattern)

内容大纲:

  • 一句话介绍单例模式
  • 单例模式的使用场景以及优缺点
  • 单例模式的实现种类
  • 用Python实现单例模式的最佳实践
  • 参考资料

1. 一句话介绍单例模式

单例模式是一种创建型设计模式,它保证某个类在全局作用域内只有一个实例对象,并为该唯一的实例对象提供全局可访问的方法。

2. 单例模式的使用场景以及优缺点

单例模式适用于以下场景:

  • 全局共享资源:当系统中某个类的实例只需要存在一个,且需要被多个对象共享访问时,可以使用单例模式。例如,日志记录器、数据库连接池等。
  • 配置信息管理:当系统中需要管理全局的配置信息,且配置信息需要被多个对象共享访问时,可以使用单例模式。例如,系统配置、应用程序配置等。
  • 线程池、连接池等资源管理:当系统中需要管理共享的资源池,且需要确保资源的一致性和有效性时,可以使用单例模式。例如,线程池、数据库连接池等。
    简单来说,单例模式往往用来封装全局变量。

单例模式的优点包括:

  • 节省系统资源:单例模式确保一个类只有一个实例存在,节省了系统资源,避免了多次实例化造成的内存浪费。
  • 全局访问点:单例模式提供了一个全局访问点,方便客户端代码对类的实例进行访问和操作。
  • 避免多重初始化问题:单例模式可以防止在多个地方重复初始化类的实例,保证了实例的唯一性和一致性。

但单例模式也存在一些缺点:

  • 单例模式破坏了类的单一职责原则,因为它同时负责类的实例化和提供全局访问;
  • 单例模式的实现可能会引入全局状态,使得代码的可测试性和可维护性降低;
  • 单例模式的使用可能会增加代码的复杂度,并且对多线程环境的支持需要额外的考虑和实现,否则可能会引发线程安全问题。

3. 单例模式的实现种类

单例模式一般分为饿汉式和懒汉式。

3.1 饿汉式

饿汉式单例模式是指在加载类的时候就完成了唯一实例对象的实例化。

它的好处是,由于它在程序启动阶段就完成了唯一实例的初始化,因此,在多线程环境下,不存在多个线程同时尝试初始化实例的问题。

另一方面,饿汉式存在以下缺点:

  • 资源浪费:由于在类加载时就创建实例,因此无论是否需要使用该实例,都会占用内存空间,可能导致资源浪费,尤其是在实例较大或者创建开销较大时;
  • 可扩展性差:饿汉式单例模式在类加载时就创建实例,如果后续需要对实例进行延迟加载或者懒加载的话,会比较困难;
  • 无法处理异常:在类加载时就创建实例的过程中,如果初始化过程中抛出异常,可能会导致类加载失败,进而影响整个应用程序的启动;
  • 失去延迟加载的优势:在一些场景下,延迟加载是一种比较好的选择,可以根据实际需要来创建实例,而饿汉式失去了延迟加载的优势;

因此,需要根据具体的业务需求和场景来选择是否使用饿汉式单例模式。

3.2 懒汉式

懒汉式单例模式指的是只有在请求获取唯一实例时才会尝试去创建实例,如果在首次请求时还没有创建,就创建一个新的实例,如果已经创建,就返回已有的实例,意思就是需要使用了再创建,所以称为“懒汉”。

相比于饿汉式,懒汉式具有如下优点和缺点:

  • 优点:

    • 延迟加载:懒汉式单例模式实现了延迟加载,即在需要使用时才创建单例实例,可以节省内存空间和系统资源;
    • 资源节约:由于实例在首次使用时才创建,因此避免了在类加载时就创建实例可能导致的资源浪费。
  • 缺点:

    • 线程安全性问题:懒汉式单例模式在多线程环境下可能存在线程安全问题,如果多个线程同时进入到实例创建的判断条件中,可能会导致创建多个实例的情况发生;
    • 性能问题:懒汉式单例模式在多线程环境下需要使用同步锁等机制来保证线程安全性,这会增加代码的复杂度并且可能会影响性能;
    • 可能存在双重检查锁失效问题:懒汉式单例模式通常使用双重检查锁来保证线程安全性,但是在一些特定的情况下可能会出现双重检查锁失效的问题,导致创建多个实例。

4. 用Python实现单例模式的最佳实践

用Python实现懒汉式的单例模式时需要注意多线程环境下重复创建实例的问题,而__new__魔法函数是线程安全的,所以可以使用这个特性实现线程安全的懒汉式单例模式:

""" 使用类的__new__方法实现线程安全的单例模式 """
class Singleton:
    __initialized = False  # 防止重复初始化
    __instance = None
    
    def __new__(cls) -> "SIngleton":
        if not cls.__instance:
            cls.__instance = super().__new__(cls)
        return cls.__instance
    
    def __init__(self):
        if not Singleton.__initialized:
            print("initiallize once")

测试多环境下上述单例类创建的实例对象是否唯一:

def testSingletonUnderMultiThreading():
    # 测试多线程环境下,单例模式是否正确
    import threading

    def func(q):
        # print("func")
        s = Singleton()
        print(id(s))
        q.add(id(s))

    # q = queue.Queue()
    q = set()
    threads = []
    for _ in range(1024):
        thread = threading.Thread(target=func, kwargs={"q": q})
        thread.start()
        threads.append(thread)
    for thread in threads:
        thread.join()
    assert len(q) == 1

5. 参考资料